This page describes the conventions used when developing ZPE/YASS itself, writing native Java functions, creating modules and objects, and building extension libraries. Following these conventions keeps extensions consistent with the ZPE runtime and makes them easier to maintain, document and debug.
Conventions
A convention describes the standard way in which a feature should be implemented. Code may technically work without following every convention, but code intended for inclusion in ZPE or distribution through ZULE should follow them consistently.
Native return values
All values crossing from native Java code into YASS should use the ZPE type
system. Native functions and module methods should therefore return an
implementation of ZPEType, rather than exposing
an arbitrary Java object directly.
Common ZPE value types include:
- String
- Number
- Boolean
- List and array
- Map and ordered map
- Function
- Object and structure
- Record
- Undefined
Use the appropriate ZPE implementation, such as
ZPEString,
ZPENumber,
ZPEBoolean,
ZPEList or
ZPEMap.
A Java value that has no direct ZPE representation should be wrapped in a
suitable ZPE object. For example, a Java
BufferedImage should be held internally by an
image object which exposes safe native methods for reading, modifying and
saving it.
Do not expose implementation-specific Java objects directly to YASS code. Wrapping them preserves the ZPE type system, permission checks, object behaviour and compatibility with ZPEX.
Native parameters
Modern native module methods receive their arguments as an array of
ZPEType values together with the active
ZPERuntimeEnvironment. A native method should
validate the number and types of its arguments before using them and should
report invalid input through ZPE's exception system.
Native methods must also accurately declare their parameter requirements, return types, permission level, documentation and version information. This information is used by the manual, the compiler, ZIDE and other development tools.
Naming conventions
YASS uses lowercase names separated by underscores. Native functions, module methods, reference methods, keywords and public library functions should follow this convention.
For example, use:
file_get_contents ZPE::list_zpe_modules calculate_total
Avoid publicly exposing camel-case names such as
getFileContents. Java implementation classes may follow normal
Java naming conventions, but the names visible to YASS should use lowercase
words separated by underscores.
This is a requirement for code intended for inclusion in ZPE or publication through ZULE. Consistent naming is particularly important because these names also appear in documentation, autocomplete suggestions and compiler diagnostics.
However, modules and objects may use camel case, and it's encouraged. So for example,
the sequential file object is written as SequentialFile.
Creating native Java libraries
ZPE is written in Java and can be extended with native functions, objects and
modules. A modern native library should implement
ZPELibrary, which extends
ZPEPluginSuperClass.
A library supplies:
- Its name and version information
- A map of native functions
- A map of native object factories
- A map of native modules
- The operating systems it supports
Native libraries should be distributed as non-runnable JAR files. They should contain the library implementation and its required resources, but should not define an application entry point merely to launch the library.
Keep the public API small and return only ZPE-compatible values. Platform- specific functionality must be identified accurately using the operating system support methods supplied by the library interface.
Submitting libraries to ZULE
ZULE is the ZPE package repository for distributing approved extensions and libraries. Before submission, a library should compile cleanly, follow the conventions in this document and include accurate documentation for every public function, object and module.
Submitted code should not bypass ZPE permission checks, expose unsafe Java objects, depend on undocumented internal behaviour or include unnecessary executable entry points. Platform restrictions and external dependencies must be clearly documented.
Packages are reviewed before publication. The current ZULE submission process should be used rather than relying on older GUI submission workflows described by previous versions of this document.
Debugging YASS programs
ZPE includes a socket-based debugger for debugging YASS programs. This is the debugger used by ZIDE to provide breakpoints, variable inspection, stepping and runtime profiling.
The debugger consists of two parts:
- The debugger interface, normally ZIDE, opens a server socket and waits for ZPE to connect.
- ZPE runs the YASS program in debug mode and sends debugging events to the interface over the socket.
Starting a debugging session
ZPE can run a YASS file in debug mode using the -d ZAC:
zpe -d program.yas
By default, ZPE connects to a debugger listening on
localhost using port 45678. A different port can be
specified when required:
zpe -d program.yas -port 5000
The debugger server must be started before ZPE is launched. If no server is listening, ZPE will report that it could not connect to the selected debugging port. Debugging connections are restricted to the local computer.
ZIDE handles this process automatically. It opens the debugger server, starts ZPE as a separate process using the corresponding port and then waits for ZPE to establish the connection.
The following simplified code is based on ZIDE. It opens a local debugger
server, launches ZPE with the same port and asks
ZPEDebugger to process the incoming events:
ServerSocket debugServer = new ServerSocket(0); int debugPort = debugServer.getLocalPort(); ProcessBuilder processBuilder = new ProcessBuilder( "java", "-jar", zpeJarPath, "-d", yassFile.toAbsolutePath().toString(), "--silent", "--use_zpe_events", "-port", Integer.toString(debugPort) ); Process zpeProcess = processBuilder.start(); // These streams must be consumed so that the ZPE process cannot block // when its output buffers become full. readProcessOutput(zpeProcess.getInputStream()); readProcessErrors(zpeProcess.getErrorStream()); // Accept ZPE's connection and process its debugger events in the background. ZPEDebugger.respond(debugServer);
Using port 0 asks the operating system to choose an available
port. ZIDE then passes that selected port to ZPE. This avoids assuming that a
fixed port is available.
ZIDE registers a breakpoint listener before starting the debugging session:
ZPEDebugger.addBreakPointReachedListener(
(breakpoint, variables) -> {
Platform.runLater(() -> {
currentBreakpoint = breakpoint;
showVariables(variables);
showDebugControls();
});
}
);
The supplied BreakPoint object represents the paused ZPE
process. ZIDE's Continue, Step Over and Stop buttons call the corresponding
methods on that object:
private void continueDebugging() { if (currentBreakpoint != null) { currentBreakpoint.resume(); } } private void stepOver() { if (currentBreakpoint != null) { currentBreakpoint.stepOver(); } } private void stopDebugging() { if (currentBreakpoint != null) { currentBreakpoint.stopExecution(); } }
Internally, these methods send a newline-terminated command back to ZPE:
Incoming events travel in the opposite direction as newline-delimited JSON. For example, a breakpoint event contains the event name and the variables visible at the point where execution paused:
continue step stop
{
"event": "breakpoint_hit",
"line": "0",
"vars": {
"$counter": 42,
"$name": "Jamie"
}
}
ZPEDebugger.respond accepts the socket
connection, decodes these JSON messages and turns them into higher-level Java
listeners and BreakPoint controls. An editor using ZPE as a
dependency can therefore reuse this implementation rather than recreating the
wire protocol.
Because ZIDE uses JavaFX while the debugger reads from a background thread,
interface changes are passed to Platform.runLater. Other user
interface toolkits should use their equivalent mechanism for transferring
work onto the interface thread.
Breakpoints
Breakpoints allow execution to pause at a selected point in a YASS program. Internally, a breakpoint is represented using:.
#breakpoint#
When ZPE reaches this marker during a debugging session, it sends a
breakpoint_hit event to the debugger. The event includes the
variables available at that point in the program.
Developers should normally create and remove breakpoints through ZIDE. ZIDE manages the internal breakpoint markers and associates them with the appropriate source lines.
Continuing and stepping
After reaching a breakpoint, ZPE waits for an instruction from the debugger. The debugger can tell ZPE to continue normally or to step through subsequent YASS statements.
ZIDE provides controls for:
- Continuing execution
- Stepping to the next statement
- Inspecting variables at a breakpoint
- Stopping the running program
Debugger communication
ZPE and the debugger communicate using newline-delimited JSON messages over a local TCP socket. ZPE sends events such as:
connectedbreakpoint_hitfailureprofile_sampleprofile_completedone
The debugger responds with commands that control execution. New debugger integrations should use this protocol rather than attempting to control the ZPE process through its standard output.
Profiling
A debugging session can also provide profiling information. ZPE reports elapsed runtime, memory usage, CPU usage, thread count and the YASS function executing when each sample was taken.
ZPEX samples more frequently because native YASS programs can finish extremely quickly. Profiling can affect runtime performance, so profiling measurements should be treated as diagnostic information rather than exact benchmark results.
Using the debugger outside ZIDE
Another development environment can attach to the YASS debugger by opening a
server socket on the chosen local port and then launching ZPE with
-d and the same -port value.
The external debugger must accept ZPE's connection, read its JSON events and respond to breakpoint and stepping events using the debugger protocol. ZPE remains the debugger client; the editor or debugging tool acts as the server.
Building native plugins
A native ZPE plugin is a Java library that implements
ZPELibrary. The plugin can provide native
functions, objects and modules to YASS.
The plugin JAR must contain a public class named
Plugin in the default package. This class is the
entry point loaded by ZPE and should implement
ZPELibrary.
A plugin must provide:
-
getName(), which returns the plugin's unique name. -
getVersionInfo(), which returns its version information. -
getFunctions(), which returns its native YASS functions. -
getObjects(), which returns factories for its native objects. -
getModules(), which returns its native modules. -
supportsWindows(),supportsMacOs()andsupportsLinux(), which describe its platform support.
A minimal plugin entry point has the following form:
public class Plugin implements ZPELibrary { @Override public String getName() { return "example"; } @Override public String getVersionInfo() { return "1.0"; } @Override public Map<String, ZPECustomFunction> getFunctions() { return new HashMap<>(); } @Override public Map<String, BiFunction<ZPERuntimeEnvironment, ZPEPropertyWrapper, ZPEObject>> getObjects() { return new HashMap<>(); } @Override public Map<String, ZPEModule> getModules() { return new HashMap<>(); } @Override public boolean supportsWindows() { return true; } @Override public boolean supportsMacOs() { return true; } @Override public boolean supportsLinux() { return true; } }
Compile the project against the current ZPE JAR and package the compiled
classes and resources into a non-runnable JAR. Plugin filenames should begin
with zpe.lib. and end with .jar.
Install the JAR in the native-plugins directory inside ZPE's
application-data directory. The active ZPE installation path can be found
using:
zpe -h config
Plugins should be tested against the same ZPE version used to compile them. They should not depend unnecessarily on package-private runtime behaviour. Native Java plugins are supported by JVM-based ZPE but are not dynamically loaded by ZPEX.
Building transpilers
A ZPE transpiler converts the compiled YASS syntax tree into another
programming language. A transpiler should implement
ZPESyntaxTranspiler.
public interface ZPESyntaxTranspiler { String transpilerName(); String transpile( IAST code, String className ); String getLanguageName(); String getFileExtension(); }
The transpile method receives the compiled
IAST, not the original YASS source. The
transpiler should traverse this tree and generate equivalent source code in
its target language.
External transpilers use the package:
jamiebalfour.zpe.transpilers
Their class and JAR names must identify the target language. For example, a Python transpiler should use the JAR file name zpe.transpiler.python.jar and the Java package:
jamiebalfour.zpe.transpilers.ZPEPythonTranspiler
The transpiler JAR should be placed in the transpilers directory
inside ZPE's application-data directory. It can then be used from ZIDE or
from the command line:
//Python is defined in the plugin as the language name
zpe -e program.yas -o program.py -t Python -n Program
getLanguageName() supplies the language identifier shown by ZPE
and ZIDE, while getFileExtension() supplies the default output
extension. transpilerName() should identify the transpiler
implementation itself.
Understanding the IAST
Plugin developers usually work with ZPE values and runtime APIs. Transpiler
developers, compiler contributors and language-tool developers must also
understand the IAST, or inline abstract syntax
tree.
An IAST node contains a byte-code
type, an optional identifier or value, source positions and a
number of links to other nodes:
-
nextnormally links statements in sequence. -
leftandmiddlecontain structural or expression branches whose meaning depends on the node's byte code. -
valuemay contain a literal value, metadata or anotherIASTbranch. -
ididentifies variables, functions, modules and other named elements where applicable. -
programStartandprogramEndretain the node's relationship with the original source.
The meaning of these links is not identical for every byte code. A
transpiler must interpret each node according to its specific structure
rather than assuming, for example, that left always represents
the same kind of child.
Before implementing a new node, observe how the same construct is represented in ZPE's AST whitepapers. The AST_WP documentation contains diagrams showing the shapes produced for YASS statements and expressions.
It is also useful to compile small, isolated examples and inspect their trees using ZPE's tree-view mode:
zpe -t example.yas
Compare the resulting tree with the relevant AST_WP diagram before adding a transpilation rule. Test braced and non-braced forms where YASS supports both, as different surface syntaxes may deliberately compile into the same IAST representation.
Transpilers should operate on byte-code constants from
YASSByteCodes rather than hard-coded numeric
values. They should also handle unknown nodes explicitly so that a newly
introduced YASS feature does not silently generate incorrect target code.

There are no comments on this page.
Comments are welcome and encouraged, including disagreement and critique. However, this is not a space for abuse. Disagreement is welcome; personal attacks, harassment, or hate will be removed instantly. This site reflects personal opinions, not universal truths. If you can’t distinguish between the two, this probably isn’t the place for you. The system temporarily stores IP addresses and browser user agents for the purposes of spam prevention, moderation, and safeguarding. This data is automatically removed after fourteen days. Your email address is stored so that replies can be sent to your email address.
Comments powered by BalfComment