SPAD is a small JVM-targeted language for projects, automation, and directive-driven workflows. The repository contains the compiler, the runtime helpers it emits against, and the Dragon package/build tooling that sits next to the language core.
This README is a practical guide. It explains what each major file does, how the compiler pipeline works, and how to run the common workflows without needing to read the source first.
SPAD is designed around a simple pipeline:
- Source text is lexed into tokens.
- Tokens are parsed into an AST.
- The static checker validates types and control flow.
- The emitter converts SPAD to Java.
javaccompiles the generated Java into classes.- The runtime helpers in
main/SpadPrelude.javaandmain/DragonToolkit.javaprovide the shared behavior that emitted programs use.
main/contains the compiler front-end, AST, type checker, emitter, and runtime support.dragon/contains the Dragon package and lockfile tooling.packages-local/contains local package metadata and example packages.scripts/contains build and install entry points for Windows and Unix-like systems.SPAD_SPEC.mdis the normative language and tooling specification.
The compiler entrypoint in main/main.java is the best place to understand the executable flow. It does not implement language semantics itself; instead it orchestrates the pipeline.
// The CLI entrypoint only catches top-level failures and prints one short message.
public static void main(String[] args) {
try {
run(args);
} catch (Exception ex) {
System.err.println("SPAD compile failed: " + ex.getMessage());
System.exit(1);
}
}That block keeps user-facing failures readable. The real work happens in the next stage.
// The compiler reads source, parses it, type-checks it, emits Java, compiles Java, and optionally runs the result.
private static void run(String[] args) throws Exception {
if (args.length == 0 || hasArg(args, "--help") || hasArg(args, "-h")) {
printUsage();
return;
}
CliOptions options = parseOptions(args);
String source = Files.readString(options.inputFile);
CompileResult result = compileSource(source, options.className, options.outDir, options.inputFile);
}The compiler pipeline is intentionally explicit so each step can be debugged separately.
Build the project first.
scripts\build-all.cmdCompile a SPAD file to JVM classes.
java main.main main/main.spad --out build/spad --class SpadProgramRun the compiled program immediately after compilation.
java main.main main/main.spad --out build/spad --class SpadProgram --runEmit a bundle with a .jre extension.
java main.main main/main.spad --out build/spad --class SpadProgram --jreThe CLI accepts these common flags:
--out <dir>sets the build output directory.--class <Name>sets the generated Java class name.--runruns the generated class after compilation.--print-javaprints the generated Java source.--jrewrites a runnable bundle.--jre-out <file.jre>overrides the bundle path.
The options parser in main/main.java is deliberately strict so invalid inputs fail early instead of producing confusing compiler output.
// The parser validates the input file, derives a class name when needed, and stores every selected option in CliOptions.
private static CliOptions parseOptions(String[] args) {
Path inputFile = null;
Path outDir = Paths.get("build", "spad");
String className = null;
boolean runCompiledClass = false;
boolean printJava = false;
boolean emitJreBundle = false;
Path jreOutputPath = null;
}// An untyped variable is inferred from its initializer.
var count = 10
// An explicit type annotation makes the intended value domain clear.
var total: Int = 0
The first line shows inference, while the second line shows a declaration that is checked against an explicit type.
// The function keyword defines a reusable block with named, typed inputs.
function add(a: Int, b: Int): Int {
return a + b
}
This example demonstrates the usual pattern: typed parameters, a typed return value, and an explicit return statement.
// elif is the preferred chained-branch form.
if (score > 10) {
print("high")
} elif (score > 5) {
print("medium")
} else {
print("low")
}
The language uses braces for blocks so the structure is always visually obvious.
// The range operator is inclusive in the standard for-range form.
for i in 0..10 {
print(i)
}
This loop counts through the range and is designed to be easy to read in automation scripts.
// try / except / finally gives the language a predictable error-handling shape.
try {
riskyOperation()
} except (e) {
handle(e)
} finally {
cleanup()
}
The try block contains the risky code, the except block handles failure, and the finally block performs cleanup.
// Pipelines pass the previous result into the next step for readable data flow.
var out = data |> normalize() |> summarize()
This pattern is useful when you want project logic to read left-to-right.
The emitted Java source uses shared helpers from the runtime layer.
// The prelude gives emitted programs a small, reusable standard library.
DragonToolkit.bootstrap("SPAD", "0.1.0");That bootstrap call is one of the first things emitted programs run so the runtime can initialize itself consistently.
The runtime currently provides helpers such as printing, truthiness checks, number conversion, string conversion, and a few collection utilities.
A SPAD compilation usually produces:
- generated Java source under
build/spad/generated-src/ - compiled
.classfiles underbuild/spad/classes/ - optionally, a
.jrebundle for distribution
That separation makes the compiler easier to inspect because you can look at the emitted Java before you decide whether to run it.
SPAD_SPEC.mdis the language reference and should stay aligned with the parser, type checker, and emitter.main/StaticTypeChecker.javaenforces language rules before code generation.main/JavaEmitter.javadecides how SPAD maps to Java.main/SpadPrelude.javacontains runtime behavior that emitted programs rely on.
If you are changing the language, update the spec and the implementation together so the docs do not drift away from the compiler behavior.
- Read
SPAD_SPEC.mdfor the normative grammar and semantics. - Inspect
main/main.javato see the compile pipeline end to end. - Run
scripts\build-all.cmdafter changes to verify the whole toolchain still compiles.