This is the final part of the quick guide.
To wrap things up, let's look at how you can run programs written in other languages from VCSSL.
To invoke a program written in a compiled language like C or C++, the general approach is to call the compiled executable directly (e.g. .exe on Windows, .out or similar on Linux).
To make things easier to follow, let's create a simple C program as an example.
# include <stdio.h>
int main(int argc, char *argv[]) {
printf("Hello C and VCSSL!\n");
return 0;
}
example.c
Compile this using your preferred compiler, and generate an executable named:
We won't go into the compilation steps here, since plenty of C tutorials already cover that in detail.
Once you've compiled it, running the executable should print the following to standard output:
If that works, you're good to move on.
The simplest way to run that executable from VCSSL is to use the system or exec functions. These run the program in a few steps:
import File;
// Execute example.exe
// ("argA/B/C" are arguments passed to the program)
string programPath = getFilePath("example.exe");
system(programPath, "argA", "argB", "argC");
Exec.vcssl
If all you want is to call it, it doesn't get easier than this. But let's be honest --
Things like:
...all of that takes more effort than just writing the call itself.
So instead of starting with the simplest case, let's jump straight to the practical one: A slightly longer setup, but far more useful.
We'll use the Process library for this:
import File;
import Process;
// Get absolute path to example.exe
// (Using a relative path or just the filename may fail depending on the environment)
string programPath = getFilePath("example.exe");
// Define arguments (program path + command-line args)
string processArgs[] = { programPath, "argA", "argB", "argC" };
// Create process
int processID = newProcess(processArgs);
// Start execution
startProcess(processID);
// Optional: send input if needed
// (Be aware: input may not be accepted unless it ends with a newline)
setProcessInput(processID, "Hello!" + EOL);
// Wait for process to finish
waitForProcess(processID);
// Get standard output and error
// (The entire output during execution is buffered)
string processOutput = getProcessOutput(processID);
string processError = getProcessError(processID);
// Output to VCSSL console
println("Standard Output: " + processOutput);
println("Standard Error: " + processError);
SerialExecution.vcssl
When you run this, you'll get:
Here, the external program is launched using newProcess(...), which:
Using that process ID, you can:
If the external program prints non-ASCII output, like Japanese text, you may encounter character encoding issues.
This happens when the output encoding of the external program doesn't match what VCSSL expects.
For example, on Windows, many old C programs output Japanese text in CP932 encoding (roughly equivalent to Shift_JIS).
In such cases, add these lines before starting the process:
// Create process
string processArgs[] = { programPath, "argA", "argB", "argC" };
int processID = newProcess(processArgs);
// Set I/O encoding
setProcessInputEncoding(processID, "CP932");
setProcessOutputEncoding(processID, "CP932");
setProcessErrorEncoding(processID, "CP932");
// Start execution
startProcess(processID);
...
Encoding.vcssl
Without this, you may see garbled output for Japanese program. Note that VCSSL uses UTF-8 by default if no encoding is specified.
Previously, we saw how to retrieve the standard output and standard error from an external executable after it finishes running.
Alternatively, you can also capture them in real time using event handlers like this:
import File;
import Process;
// Get the absolute path of example.exe
//(Relative paths or just the filename might fail depending on the environment)
string programPath = getFilePath("example.exe");
// Create and run the process for example.exe
// ("argA/B/C" are arguments passed to the program)
string processArgs[] = { programPath, "argA", "argB", "argC" };
int processID = newProcess(processArgs);
startProcess(processID);
// Event handler for standard output
void onProcessOutput(int sourceProcessId, string text) {
print(text);
}
// Event handler for standard error
void onProcessError(int sourceProcessId, string text) {
print(text);
}
EventExecution.vcssl
With this setup, whenever the running program outputs something to standard output or standard error, the onProcessOutput or onProcessError function is triggered accordingly.
The arguments passed to these handlers are:
Note, however, that the handler is not triggered every single time a byte is output. Instead, the output is buffered to a certain extent before being passed to the event.
Usually, the output is buffered per line, so in most cases, the argument "text" will contain a full line of output.
You might be wondering, "Why bother with event handlers for this?" The answer is: when interactive input/output is required.
Many command-line programs behave like this:
In such cases, you must respond at the correct moment based on the output, or the process will hang. Using event handlers lets you detect output in real time and respond dynamically based on its content.
Until now, we've assumed you're calling a C-language program. But what if you want to run OS-dependent commands?
In such cases, the most reliable approach is:
Technically, you can pass command strings directly to the shell (as a one-liner), depending on the shell you're using.
But honestly, invoking external programs is inherently difficult to debug, so it's wise to expect trouble (e.g. relative paths that work in the shell but fail when called from a one-liner in your program due to different working directories, or argument quoting issues where "C:\Program Files\something" gets split).
All of these are much easier to troubleshoot when written as a shell script. In fact, it's common to end up saying, "I should've just made a script from the start."
Along similar lines, you might try calling the interpreter of another scripting language directly to run a script.
This might work, in principle, since the interpreter is just an executable too.
However, whether it works reliably depends entirely on the implementation of the interpreter being called.
Scripting language interpreters tend to be more complex than typical executables, and even small mismatches in environment or arguments can prevent them from running properly.
So it's best to treat this approach with a "fingers crossed" mindset -- if it runs, great! If not, you may need to dig into interpreter -- specific docs and behavior
Now for the second half of this guide. This time, we'll look at how to call code written in the Java programming language from VCSSL.
Why cover Java separately from other languages? Because Java code can be called in a different way. You see, the VCSSL runtime itself is written in Java, and many of the built-in functions are actually implemented in Java.
That means there's already an interface available to implement your own "built-in functions" in Java, which can then be called from VCSSL just like any other function. Let's take a look at how to use that.
There are several kinds of interfaces available for use, but most of the publicly documented ones (like those listed here: VCSSL/Vnano Plugin Interface GitHub Repo) are designed primarily for Vnano, not VCSSL.
Still, there's one interface that's been supported since the early days -- while it's not ideal for ultra-high-frequency use (due to its call overhead), it's simple to use and works well in many situations:
Let's try using this interface.
You could grab the interface declaration file from the link above, but setting up the package path and classpath can be a bit of a hassle.
So instead, just copy and paste the following code into a file named "GeneralProcessConnectionInterface2.java" and save it wherever you like:
public interface GeneralProcessConnectionInterface2 {
// Initialization logic before the script is executed
public void init();
// Cleanup logic after the script is done
public void dispose();
// Return true only for the function names you want to process
public boolean isProcessable(String functionName);
// Implement the logic to be executed when VCSSL calls the function
public String[] process(String functionName, String[] args);
}
GeneralProcessConnectionInterface2.java
Since this will be loaded via reflection, as long as the method names and signatures match, you're good to go. Just compile it like this:
This interface declares four methods, and each one has a specific role -- refer to the inline comments in the code above for a quick overview.
Now let's create a separate Java class that implements the interface we defined earlier, and write the processing logic we want to use.
In VCSSL and Vnano, Java-based components that provide built-in functions or other runtime features are called plugins.
So what we're doing here is writing a plugin. Let's call the class ExamplePlugin:
public class ExamplePlugin implements GeneralProcessConnectionInterface2 {
// Initialization logic before the script is executed
@Override
public void init() {
System.out.println("Initializing plugin...");
}
// Cleanup logic after the script finishes
@Override
public void dispose() {
System.out.println("Disposing plugin...");
}
// Return true only for the function(s) you want to handle
@Override
public boolean isProcessable(String functionName) {
if (functionName.equals("exampleFunction")) {
return true;
}
return false;
}
// Main logic to execute when the function is called from VCSSL
@Override
public String[] process(String functionName, String[] args) {
if (functionName.equals("exampleFunction")) {
return new String[]{ "exampleFunction was called: arg0=" + args[0] + ", arg1=" + args[1] };
}
System.err.println("!!! If this gets printed, the function name was likely mistyped.");
return null;
}
}
ExamplePlugin.java
So what does this plugin do?
Now compile the plugin in the same folder where you saved the compiled GPCI2 interface GeneralProcessConnectionInterface2.class:
If ExamplePlugin.class is generated successfully, your plugin is ready.
Let's now try calling the plugin from VCSSL.
In the same folder where you have ExamplePlugin.class, create a VCSSL script file and write the following:
// Load the plugin ExamplePlugin.class
connect ExamplePlugin;
// Call the function "exampleFunction" provided by the plugin
string ret[] = exampleFunction("Hello", "World!");
// Output the result
println(ret);
ExamplePluginCall.vcssl
If everything goes well, you should see the following output:
Just like that, the message generated on the plugin side has been successfully returned to VCSSL. Success!
As we've seen above, calling Java methods via the GPCI2 interface is quite simple -- and for many use cases, it's already more than sufficient. That's why GPCI2 is likely to remain supported for the foreseeable future.
However, since this interface was designed in the early days of VCSSL, it comes with a few legacy quirks. Some of them result in extra overhead, others make things feel a little -- too "loose," and a few are just plain awkward. They're not necessarily flaws, but more like charming oddities rooted in the history of the platform.
Let's go over what to watch out for, and how to work around these quirks when needed.
GPCI2 has very loose type constraints. For instance, even though "exampleFunction" was intended to take strings, you can still call it like this:
string ret[] = exampleFunction(123, 4.56, true);
QuirksAnyType.vcssl
All arguments passed from VCSSL are automatically converted to strings before being passed to the plugin's "args" array. This is incredibly flexible -- perhaps too flexible for a statically typed language.
This can be problematic when, for example, your plugin assumes the arguments are integers but VCSSL users pass in "ABCDE" or some other non-numeric string. In statically typed code, you'd normally expect the compiler to catch this mismatch for you.
So how do we avoid this? The common workaround is to wrap your plugin function inside a VCSSL function, like this:
// Define a wrapper function in VCSSL that enforces strict typing
string wrapperFunction(int arg0, int arg1) {
return exampleFunction(arg0, arg1);
}
QuirksAnyTypeWrap.vcssl
If you define a wrapper like this in a separate VCSSL library file and import it into your main script, VCSSL will enforce type checking before calling the plugin.
Here's another gotcha:
Logically, you might expect args.length == 0, but in practice, you get:
There's no real reason for this. Itfs just a legacy quirk from the very first VCSSL engine:
The real problem is that your plugin can't distinguish between these two cases:
If this distinction matters for your logic, the only safe workaround is again to use a VCSSL wrapper function and create separate overloads for the two use cases.
In the future, GPCI3 (the successor to GPCI2) is expected to offer a compatibility mode switch that will allow more precise handling of this situation, but it's not available yet.
In GPCI2, you can call a plugin function with a single array argument like this:
string args[] = {"123", "4.56", "true"};
string ret[] = exampleFunction(args);
QuirksArrayArg.vcssl
However, you can't pass multiple arrays. Only one array can be passed as a single argument.
If you must pass multiple arrays, you'll need to flatten and serialize them into a single array, embedding any structure/length info manually. This is a limitation of GPCI2 that more modern interfaces don't have.
As you may have noticed, GPCI2 is quite "free-form" when it comes to types. Internally, the engine performs type conversions to string arrays for every call, and that alone introduces significant overhead.
On top of that, every plugin call involves a runtime check like this:
// Main logic to execute when the function is called from VCSSL
@Override
public String[] process(String functionName, String[] args) {
if (functionName.equals("exampleFunction")) {
...
QuirksOverhead.java
So every time the function is called from VCSSL, this string comparison is executed. That adds up quickly, especially in tight loops or high-frequency scenarios like numerical simulations.
Therefore, it's not a good idea to implement performance-critical logic using GPCI2.
Unfortunately, the faster plugin interfaces that avoid this overhead aren't currently supported in the VCSSL engine (they are supported in Vnano, though). We'd like to support those in VCSSL as well in the future -- especially if there's demand -- but as of now, there's no real workaround.
And with that, this quick-start guide comes to an end!
Over the course of five sessions, we've taken a fast-paced tour through various parts of VCSSL to help you get a taste of the language's overall capabilities. How did you find it?
If this guide has helped you get a rough sense of "what kinds of features VCSSL offers" and "how to approach typical use cases," then it has achieved its goal -- and I'm glad it could be of help.
If you'd like to dive deeper into specific topics, there are more detailed guides available for free on the official VCSSL website:
If you're interested in exploring more deeply, feel free to make full use of the resources above!