The YLang Book
Welcome to The YLang Book, the official online guide to the YLang programming language, its compiler, and its tools.
This book is organized as a guided course and a technical reference. You can read it from the beginning to learn the language step by step, or use the chapter sidebar and search to jump directly to a feature. Code examples are copyable, the appearance can be changed, and previous/next navigation helps you move through the chapters.
The online edition is the canonical book. It is maintained alongside the compiler and its reference documents, so corrections can be published without maintaining a separate PDF edition. The tutorial explains how to use YLang; the reference chapters describe exact syntax, supported behavior, and known restrictions.
Which version does this book describe?
This edition documents YLang 2.2.0, the current stable release at the time of writing.
The stable core uses explicit types and the default C-backend workflow. Some features added around the core are experimental and have narrower support:
- HTTP requests and environment-variable access have documented runtime and security limits.
- The source-aware terminal debugger and performance harness are tools for development and investigation.
- Structs, enums, named imports, GUI/Game2D, the NASM backend, native Windows support, and YSPM packaging have feature-specific restrictions.
- The NASM backend does not support every program accepted by the C backend.
- YLang does not claim Rust-level memory safety and does not provide a security sandbox.
- `Option
` and `Result<T, E>` remain design proposals, not supported language constructs.
Always check the relevant reference chapter before depending on an experimental feature in a project. A construct being accepted by one compiler stage does not guarantee that every backend supports it.
A recommended reading path
- Read the Introduction to understand YLang’s goals and support boundaries.
- Install YLang and verify that the compiler and C toolchain work.
- Write, check, build, and run your first program.
- Learn the compiler workflow so you can distinguish source checking, code generation, and native compilation.
- Study program structure, types and conversions, and functions and modules.
- Explore the standard library, then use the reference chapters when you need exact details.
- Try GUI and Game2D only after the command-line workflow is working.
How to use this book
- Tutorial chapters introduce a topic, explain why it matters, and walk through small examples.
- Reference chapters document syntax, contracts, error codes, platform requirements, and limitations.
- Release notes and roadmaps describe a particular release or proposal. A design document is not proof that a feature has shipped.
- Compiler diagnostics should be read with their complete error code and message; use the Compiler Error Codes chapter when investigating a failure.
For source code, issue reports, and release downloads, visit the YLang GitHub repository.
The book is continuously maintained with the project. If an example appears to disagree with the compiler, check the canonical language specification and report the mismatch with a minimal reproducible example.
Introduction
YLang is a compiled programming language designed around readable source code, explicit types, and compiler diagnostics that help you locate mistakes. The compiler is written in C17. Its default workflow checks a `.yl` source file, translates it to C, and invokes GCC or Clang to produce a native executable.
This book is both a tutorial and a technical reference. You will start with a small command-line program, learn how declarations and functions are organized, and then explore the compiler, standard library, optional graphical helpers, and platform-specific workflows.
What YLang is
YLang source code is written in `.yl` files. A program describes values and operations using the language’s own syntax. Before a native executable is produced, the compiler checks the source and reports issues such as syntax errors, unknown names, invalid assignments, and type mismatches.
The default backend generates C, so a compatible C compiler is part of the normal build process. GCC and Clang are the supported choices exposed by the command-line interface. Generated C can also be emitted for inspection when debugging a compiler issue or learning about the compilation process.
YLang also contains experimental extensions and tools. These include a restricted NASM backend, structs and enums, imports, GUI/Game2D helpers, HTTP and environment APIs, a terminal debugger, and optional Linux packaging. Each extension has its own support boundaries; the existence of a feature does not mean it is equally supported by every backend or platform.
What YLang is not
YLang is not an interpreted language, and the default compiler workflow is not an independent machine-code backend: it relies on GCC or Clang after generating C.
YLang is also not a complete systems language with every advanced feature available. In particular:
- `Option
` and `Result<T, E>` are design proposals, not current language constructs. - The experimental NASM backend supports a narrower subset than the C backend.
- GUI/Game2D requires additional native dependencies and a graphical session for live-window testing.
- Native Windows support is experimental and does not promise complete feature parity.
- YLang does not claim Rust-level memory safety or provide a security sandbox.
These distinctions are important when deciding whether a feature is appropriate for a real project. This book labels experimental functionality and links to its contract or design document wherever possible.
Stable core and experimental extensions
YLang 2.2.0 is the release described by this edition. The stable core and its default C-backend workflow form the compatibility baseline. Extensions may change or have restrictions that do not apply to the stable core.
When choosing a feature, follow this checklist:
- Read the relevant tutorial to understand its intended use.
- Open the corresponding reference or specification for exact syntax.
- Check platform and backend support.
- Build and run a small example in the same environment you intend to use.
- Keep a minimal reproducer if the compiler behaves differently than expected.
Do not infer runtime behavior from syntax alone. A program that passes one stage of compilation may still fail during native compilation, linking, or execution.
How to read examples
Most tutorial examples are small on purpose. They isolate one idea so that you can change a value, run the compiler again, and observe what changes. Unless a chapter says otherwise, commands assume that you are in the root of the cloned YLang repository and that the compiler has already been built at `build/ylang`.
Shell commands and YLang source serve different purposes:
- Shell commands such as `make` build the compiler or invoke it.
- YLang code belongs in a `.yl` source file.
- Output shown after running a program is an example of the program’s behavior, not another command to type.
If you see a mismatch between an example and the implementation, use the Complete Language Specification and report the smallest example that reproduces the problem.
Where to go next
If you have not built the compiler yet, continue to Install YLang. If you already have a working build, go straight to Your First Program. For an exact list of accepted syntax, keep the Complete Language Specification nearby.
Install YLang
This chapter explains how to build the YLang compiler from source, verify the build, and prepare the optional dependencies used by graphical examples. Building from source is the most direct way to use the compiler version documented by this book and run the project’s regression tests.
Before you begin
You need:
- Git to download the source repository.
- GCC or Clang with C17 support to compile the YLang compiler and the C it generates.
- GNU Make to run the project’s build and test targets.
- A terminal in which you can run shell commands.
Linux and macOS are the primary documented development environments. A native 64-bit Windows path using MinGW-w64 is available, but it is experimental and has restrictions described in the Windows Setup guide.
GUI and Game2D examples have extra requirements: SDL2 development files and `pkg-config`. These are not required for ordinary command-line programs.
Linux
Fedora
Install the compiler toolchains and build tools:
```sh sudo dnf install gcc clang make git ```
Debian or Ubuntu
Install the equivalent packages:
```sh sudo apt update sudo apt install build-essential clang make git ```
Clone and build
Download the repository, enter its directory, and build the compiler:
```sh git clone https://github.com/Yassine-Jemi01/ylang.git cd ylang make ```
The resulting compiler should be located at `build/ylang`. If `make` fails, read the first compiler or linker error in the output; later messages may only be consequences of that first failure.
Run the regression tests:
```sh make test ```
A successful test run is a useful check that the compiler and its expected development dependencies are working. It does not mean every experimental feature works on every platform.
macOS
Install Apple’s command-line developer tools:
```sh xcode-select –install ```
Install GNU Make and the LLVM toolchain with Homebrew:
```sh brew install make llvm git ```
Use GNU Make as `gmake` when invoking the build and test targets:
```sh gmake gmake test ```
For GUI/Game2D programs, install SDL2 and `pkg-config` as well:
```sh brew install sdl2 pkg-config ```
The compiler’s normal C-backend workflow still needs a working C compiler. If a build cannot find GCC or Clang, verify that the compiler is installed and available on your shell’s `PATH`.
Windows
The experimental native Windows path uses 64-bit MinGW-w64 from an MSYS2 MINGW64 shell. MSVC is not the supported toolchain for this milestone.
Follow the Windows Setup guide for the exact package list, environment, and current limitations. Do not assume that a build tested on Linux will have identical support on Windows.
Verify the compiler
After `make` finishes, check that the compiler executable exists:
```sh ./build/ylang ```
The compiler’s help output should list the available commands. The everyday workflow is:
```sh ./build/ylang check path/to/program.yl ./build/ylang build path/to/program.yl -o program ```
Replace the example path with an actual `.yl` file. The first command checks the source; the second creates a native executable through the default C backend.
Optional: GUI and Game2D dependencies
On Fedora, SDL2 development files and `pkg-config` can be installed with:
```sh sudo dnf install SDL2-devel pkgconf-pkg-config ```
On Debian or Ubuntu:
```sh sudo apt install libsdl2-dev pkg-config ```
On macOS, use the Homebrew command shown above. If the graphical example fails to compile, verify that SDL2’s development files are installed and that `pkg-config` can find them. If it builds but no window appears, run it from a graphical desktop session; headless environments cannot validate live window behavior.
Optional: install a Linux package
YLang also publishes an optional `.yspkg` package for compatible Linux x86-64 systems using YSPM. This is a packaging option, not a replacement for the compiler toolchain required when building from source. See YSPM Linux Packages for package requirements and installation details.
Troubleshooting
- `make: command not found`: install GNU Make, or use `gmake` on macOS.
- `gcc` or `clang` is missing: install one of the supported C toolchains and check that it is available on `PATH`.
- SDL2 is not found: install the development package, not only the runtime library, and confirm that `pkg-config` is installed.
- A test fails: preserve the first failing test’s output and command. Do not treat a partial test run as a complete pass.
- A program builds on one platform but not another: check the platform support page and the relevant backend contract before assuming the source is portable.
Next
Continue to Your First Program to create a source file, check it, build a native executable, and run it.
Your First Program
Create a file named hello.yl:
function main() -> int {
let const string name = "YLang";
print("Hello from", name);
print("The answer is", 40 + 2);
return 0;
}
The program defines the main function, creates a constant string, prints two lines, and returns an integer exit status.
Check, build, and run
From the repository root:
./build/ylang check hello.yl
./build/ylang build hello.yl -o hello
./hello
The first command checks the source without producing an executable. The second uses the default C backend and invokes a C compiler to build the native program. The final command runs it.
On Windows, the compiler executable is normally build/ylang.exe; the output program should use a Windows executable name such as hello.exe.
What if it fails?
Read the complete diagnostic, including its error code. Check punctuation, declared types, and the compiler command before changing the program. The Compiler Error Codes page explains stable diagnostic identifiers.
Try one change
Change the value of name, or replace 40 + 2 with a different integer expression. Run the same check/build/run sequence again. The point is to build a habit: check the source first, then compile it, then run the output.
The Compiler Workflow
The normal YLang workflow separates source checking from native compilation. A useful cycle is edit, check, build, run, and test. Keeping these stages distinct makes failures easier to locate: a source error is different from a missing C compiler, a linker failure, or a runtime problem.
1. Check before building
```sh ./build/ylang check hello.yl ```
Use `check` while editing. It asks the YLang compiler to validate the source and reports issues such as syntax, name, initialization, and type errors without building the final executable.
When a check fails, start with the first diagnostic. Record the file, line, column, error code, and complete message. Fix one cause at a time and run the check again; changing several unrelated things at once makes it harder to tell which change mattered.
A successful check means the source passed this compiler stage. It does not prove that a native compiler or linker will succeed, that every backend supports the same feature set, or that the program’s runtime behavior is correct.
2. Build a native executable
```sh ./build/ylang build hello.yl -o hello ```
The `-o hello` argument chooses the output path. The default backend emits C and invokes GCC or Clang. To select Clang explicitly:
```sh ./build/ylang build hello.yl -o hello –cc clang ```
The C compiler is part of this route: YLang does not yet have a complete general-purpose native backend independent of GCC/Clang. If the source check succeeds but the build fails, read the native compiler or linker output as well as the YLang diagnostic.
3. Run and verify the result
On Linux or macOS, run an executable named `hello` from the current directory with:
```sh ./hello ```
Compare the actual output with the behavior you intended. For nontrivial programs, test normal input, boundary values, and failure cases. A successful build only confirms that the compilation steps completed; it does not guarantee that the program is logically correct.
4. Inspect generated C
```sh ./build/ylang emit-c hello.yl -o build/hello.c ```
Generated C can help you understand the compilation pipeline or investigate a compiler issue. Use it as a debugging aid, not as a stable public interface unless a specific release explicitly guarantees the generated representation.
When inspecting output, ask a focused question: Was the source expression translated as expected? Is a function call missing? Did a value get converted before it was passed onward? If the generated C appears wrong, preserve the smallest YLang program that reproduces the issue and compare the result with the program’s expected behavior.
5. Assembly and the experimental NASM path
On supported Linux x86-64 systems, the compiler can emit assembly or build a program from the restricted NASM subset:
```sh ./build/ylang emit-asm examples/your-program.yl -o build/your-program.asm ./build/ylang build-asm examples/your-program.yl -o build/your-program ```
NASM is experimental. It does not support every construct accepted by the C backend, and unsupported combinations should be rejected explicitly. Do not assume that a successful C-backend build means the same source will build with NASM. Read the Backend Roadmap before choosing the assembly route for a project.
If a feature works with one backend but not the other, reduce the source to a minimal example and record which backend and compiler version you used. This makes backend-parity issues easier to reproduce.
6. Use the test suite
When working on the compiler itself, run the relevant tests after each change. The repository’s main regression target is:
```sh make test ```
For changes that affect code generation, backend parity, parsing, or runtime behavior, run the focused test as well as the broad suite when one is available. A test run is only a pass if it completes and reports success; a command that stopped early or skipped tests should not be described as a full pass.
7. Understand the failure stage
| Symptom | Likely stage to inspect |
|---|---|
| YLang syntax or type diagnostic | Source checking |
| C compiler reports an error | Generated C or native compilation |
| Undefined reference or missing library | Linking and dependencies |
| Executable starts but gives wrong output | Runtime logic or code generation |
| C backend succeeds but NASM rejects the source | Experimental backend support boundary |
| GUI builds but no window appears | Runtime environment or graphical session |
These are starting points, not guaranteed diagnoses. Read the full error and reproduce the failure before deciding what caused it.
Summary
Use `check` for fast source feedback, `build` for the default native executable, `emit-c` to inspect the C translation, and the experimental assembly commands only when their limitations fit your project. For deeper implementation details, continue to Compiler Architecture.
Program Structure
A YLang source file uses the `.yl` extension. The language makes types visible in declarations and function signatures, and most command-line programs define a `main` function that returns an integer status.
This chapter introduces the pieces that appear in many programs: functions, local variables, constants, expressions, control flow, and user-defined data. For exact grammar and compiler-specific restrictions, use the Complete Language Specification.
A complete small program
```ylang function add(int left, int right) -> int { return left + right; }
function main() -> int { let int total = add(20, 22); print(total); return 0; } ```
The first function accepts two integer parameters and returns their sum. The `main` function calls it, stores the returned value, prints it, and returns a status code. Breaking behavior into small functions makes it easier to name the purpose of each operation and test it separately.
Functions and signatures
A function declaration includes its name, parameter list, return type, and body:
```ylang function is_positive(int value) -> bool { return value > 0; } ```
The parameter’s type describes what the function expects. The return type after `->` describes the value callers receive. When changing a function signature, check every call site: callers must pass compatible arguments and use the returned value correctly.
Use the compiler to confirm the exact syntax accepted by your version. Do not assume that a feature from C, Python, Rust, or another language is available merely because its syntax looks familiar.
Local variables
A mutable local uses a declaration such as:
```ylang let int score = 10; score = score + 1; ```
The declared type is `int`; the next statement reads the current value, adds one, and assigns the result back to the same binding.
A constant declaration uses `const`:
```ylang let const string language = “YLang”; ```
Initialize a local before reading it. A constant cannot be reassigned after its declaration. Prefer constants when a value should not change, because this communicates intent to readers and prevents accidental reassignment.
Expressions and statements
An expression computes a value. Examples include an integer literal such as `42`, a variable name such as `score`, and an arithmetic expression such as `score + 1`. A statement performs an action, such as declaring a variable, assigning a value, calling a function, or returning from a function.
Keep expressions small while learning. If a diagnostic points to a long expression, split the calculation into named intermediate values; that makes the types and the source of a mismatch easier to see.
Control flow
Conditional statements and loops let a function choose and repeat work. A condition should have the type required by the language, and expressions in the body must obey the same type and initialization rules as expressions elsewhere.
When learning a control-flow construct:
- Start with the smallest program that uses it.
- Check the program before adding more behavior.
- Try a boundary case, such as a condition that is false immediately.
- Inspect the output and any diagnostic rather than guessing what the construct means.
Consult the Complete Language Specification for the supported grammar and exact restrictions.
User-defined data
YLang 2.x includes experimental structs and unit enums. Structs group named fields into a single value; enums represent one of a declared set of variants. These features are useful for modeling small domains, but their limits and backend support are not the same as Rust’s.
Continue to Structs and Enums and Matching for the actual contracts. In particular, verify support for a feature in the backend and platform you plan to use instead of assuming every accepted construct is portable.
Common mistakes
- Missing a declaration: declare a local before using its name.
- Wrong type: compare the declared type with the expression’s type and use only documented explicit conversions.
- Reassigning a constant: use a mutable declaration if the value genuinely needs to change.
- Assuming familiar syntax: check the language specification for the exact form accepted by YLang.
- Treating a design proposal as shipped: proposals and roadmaps describe intent; the implementation and release documentation define current support.
Practice
- Change the example so that `add` adds different integer values, then check and run it.
- Add a constant string describing the result and print it alongside the total.
- Deliberately assign a value of the wrong type, read the complete compiler diagnostic, and then correct the declaration or expression.
After these exercises, continue to Types and Explicit Conversions.
Types and Explicit Conversions
YLang declarations make types visible. The language does not silently convert arbitrary values between types. This makes the intended representation visible in the source and helps the compiler identify incompatible operations before the program runs.
The exact type grammar and supported operations are documented in the Complete Language Specification. This chapter explains how to think about types and how to make conversions intentionally.
Common built-in types
The common built-in types include:
- `int` for integer values.
- `float` for floating-point values.
- `bool` for boolean values.
- `char` for character values.
- `string` for text.
Choose a type based on the value’s meaning, not only on what makes an assignment compile. For example, text that happens to contain digits is still text until you parse it into a numeric value. Similarly, a numeric result should not be treated as a string until it has been formatted.
Declare values with their types
A declaration states the type at the point where a value is introduced:
```ylang let int count = 12; let float ratio = 0.5; let bool enabled = true; let const string label = “items”; ```
These examples illustrate the purpose of typed declarations; use the specification and compiler to confirm the exact forms supported by the version you are using.
A type mismatch is not necessarily a compiler obstacle to work around. It may reveal that the program is mixing two different concepts. Before converting a value, ask whether the operation makes sense for the domain.
Convert intentionally
Use explicit conversion functions when you need a different representation:
- `to_float(integer)` converts an integer to a floating-point value.
- `to_int(float)` truncates toward zero and reports a runtime error for non-finite or out-of-range values.
- `to_string(value)` formats supported scalar values as a string.
- `string.is_int(text)` checks whether the full text is a valid signed integer representation.
- `string.parse_int(text)` parses integer text with failure handling.
These functions make a conversion visible at the call site instead of hiding it inside an assignment or expression. Refer to the Complete Language Specification for the precise behavior and supported cases.
For example, when a program receives a string from a file or user input, do not assume it is a valid integer simply because it looks numeric. Validate or parse it using the documented API, and handle the failure path before using the result.
Truncation and numeric boundaries
Converting a floating-point value to an integer can discard the fractional part. The documented `to_int` behavior truncates toward zero, so it is not the same as rounding to the nearest integer. A conversion can also fail for values that are non-finite or outside the supported integer range.
When a calculation depends on a boundary, test values around that boundary and handle runtime errors as specified. Avoid relying on accidental behavior from a particular C compiler or platform.
Type errors are useful
When a value has the wrong type, use this process:
- Read the entire compiler diagnostic, including its error code and source location.
- Check the declared type of each value involved in the expression.
- Decide whether the declaration is wrong or whether the expression is combining unrelated types.
- If a conversion is semantically appropriate, use a documented conversion function explicitly.
- Run `check` again, then build and test the corrected program.
Avoid adding a conversion merely to silence an error if it changes the meaning of the calculation.
Not implemented in this release
`Option
Practice
- Declare an integer and convert it to a floating-point value using the documented function.
- Convert a numeric value to a string for display.
- Try parsing a valid integer string and an invalid one; observe how the documented API reports failure.
- Test a floating-point value with a fractional part to understand truncation toward zero.
Use the compiler and specification together: the specification explains the intended contract, while a minimal test confirms how the current build behaves.
Functions and Modules
Functions provide named units of behavior; imports let a source file use selected declarations from another `.yl` file. This chapter explains how to keep code organized as a project grows from one file to several.
Functions first
A function should have a clear purpose, a readable signature, and a body that performs a related set of operations. Parameters describe the values the caller supplies; the return type describes the value produced by the function.
```ylang function answer() -> int { return 42; }
function main() -> int { print(answer()); return 0; } ```
The caller uses the function’s name and parentheses to invoke it. If a function signature changes, check every caller for compatible arguments and return-value handling.
A local import
Given a sibling file named `math_tools.yl`:
```ylang function answer() -> int { return 42; } ```
A second file can import that function:
```ylang from “./math_tools.yl” import answer;
function main() -> int { print(answer()); return 0; } ```
Relative paths are resolved from the importing source file. The imported name becomes available to the importing source according to the module system’s current rules.
In this release, imports are experimental and do not imply a central package registry or dependency solver. Import aliases and qualified module namespaces are not part of the current contract.
Organize one module at a time
Keep each module focused on one coherent responsibility. A small math module, for example, can hold reusable calculations; the entry-point file can then coordinate input, calls, and output.
A practical workflow is:
- Create the new source file and add one small function.
- Import only the declarations the caller needs.
- Run `check` on the entry-point file after changing the import graph.
- Build and run the smallest example before moving more code.
- If the compiler reports a missing file or symbol, verify the spelling and the path relative to the importing file.
The compiler checks imported symbols and reports missing files or names; circular imports are rejected. Do not assume that an import has the behavior of a package manager or that it downloads dependencies.
Keep dependencies understandable
Prefer a simple, visible module graph. Avoid splitting a tiny example into many files before the boundaries are useful. When a module depends on another module, make that relationship explicit in the source and keep the imported API narrow.
For reproducible builds, keep the files required by the program together in the project and document any external native dependencies separately. YLang’s local import system is not a substitute for a dependency registry, version resolver, or system package manager.
Troubleshooting
- The imported file cannot be found: check the exact relative path and file extension.
- The imported function is unknown: confirm the exported declaration name and the import spelling.
- A cycle is reported: restructure the modules so their dependency graph is not circular.
- The project works in one directory but not another: review relative paths and the source file from which they are resolved.
- A module feature is rejected: consult the Module System reference; do not assume aliases or qualified namespaces are supported.
For the exact grammar, visibility rules, and restrictions, read the Module System chapter.
Practice
Create a file with a function that returns a fixed integer, import it from a separate `.yl` file, and call it from `main`. Check the entry point, build the program, and run it. Then rename the imported function deliberately and inspect the compiler’s diagnostic before correcting it.
The Standard Library
YLang’s standard library provides built-in namespaces for common tasks. The available surface is defined by the implementation and its reference documents; do not assume that a similarly named function from C, Python, Rust, or another language exists in YLang or behaves the same way.
This chapter is an orientation guide. For exact function signatures, accepted argument types, error behavior, and backend support, consult the Complete Language Specification and the feature-specific references linked below.
Available namespaces
The current documentation covers namespaces such as:
- `math` for supported mathematical operations.
- `string` for string inspection and explicit parsing/conversion helpers.
- `io` for console and file-related operations.
- `path` for path-related checks.
- `image` for supported image-related operations.
- `env.get(name)` for reading a process environment variable without launching a shell.
- `http` for optional synchronous GET/POST requests through libcurl; see HTTP and API Requests for dependency requirements and security defaults.
The namespaces do not all have the same maturity. Basic language operations and experimental additions should be evaluated separately. HTTP and environment helpers are newer experimental additions and are outside the stable-core compatibility guarantee.
Strings and parsing
Text values often arrive from files, console input, or network responses. Do not treat text as a number simply because it contains digits. Use the documented parsing and validation helpers when converting text into a numeric value.
The documented helpers include `string.is_int(text)`, which checks whether the full text is a valid signed integer representation, and `string.parse_int(text)`, which parses integer text with failure handling. Consult the specification for exact accepted formats and failure behavior.
When handling untrusted or user-provided text, validate the result before using it in arithmetic or as an index, path, or other control value.
Files, paths, and environment
Use the documented `io` and `path` operations for file-related work instead of relying on the current working directory or hand-built path assumptions. Paths can be interpreted differently across platforms, so test the same operation on every platform your application claims to support.
The `env.get(name)` helper reads a process environment variable without launching a shell. Environment variables may contain missing, empty, or unexpected values; check the documented return behavior and validate the value before depending on it. Avoid storing secrets in source code or printing sensitive environment values to logs.
HTTP requests
The HTTP namespace is an experimental integration and may require libcurl at build or runtime. Network access introduces failures that ordinary local calculations do not have: a server can be unreachable, return an error status, send unexpected content, or redirect a request.
Read HTTP and API Requests and the Security Model before using these APIs. Do not assume that HTTP requests are asynchronous or that they provide a sandbox. Apply timeouts, validate responses, and avoid sending credentials to an untrusted destination.
Backend and platform support
Do not assume every standard-library call is supported by the experimental NASM backend. The C backend remains the broad-coverage default. A source file that checks successfully may still encounter backend-specific restrictions during code generation.
For a library feature that matters to your project:
- Find its exact reference page.
- Confirm required native libraries and build dependencies.
- Check whether your selected backend and platform support it.
- Build a minimal example and test its success and failure paths.
- Keep the example in your project’s tests so regressions are easy to detect.
The Compiler Error Codes page includes diagnostics for unknown standard-library namespaces.
Optional graphical helpers
GUI and Game2D are optional and have their own SDL2 requirements. They are not required for ordinary command-line programs. Start with GUI and Game2D if you need windows, input, or simple 2D drawing.
Practice
Write a small program that converts a string containing an integer into a numeric value using the documented helper. Test both valid and invalid text. Then choose one path or environment operation and document what happens when its input is missing or unexpected.
HTTP and API Requests (Experimental)
Status: experimental development feature for YLang 2.2. HTTP functions are implemented on the C backend behind an optional libcurl dependency. The API includes synchronous calls plus an initial asynchronous task API built on libcurl’s multi interface; async/await is currently restricted to HTTP task handles, not general-purpose YLang functions.
Requirements
Install libcurl development files and pkg-config only on machines that build programs using the HTTP namespace.
- Fedora:
sudo dnf install libcurl-devel pkgconf-pkg-config - Debian/Ubuntu:
sudo apt install libcurl4-openssl-dev pkg-config - macOS:
brew install curl pkg-config; if Homebrew curl is keg-only, setPKG_CONFIG_PATH="$(brew --prefix curl)/lib/pkgconfig:$PKG_CONFIG_PATH". - Windows with MSYS2 MINGW64:
pacman -S mingw-w64-x86_64-curl mingw-w64-x86_64-pkgconf
The ordinary compiler and programs that do not call http.* do not require libcurl. ylang check validates HTTP calls without linking the HTTP runtime; native ylang build requires the development dependency when an HTTP function is used. The experimental NASM backend does not support HTTP calls. Async tasks are process-local handles with a maximum of 32 in-flight slots; each task may be consumed once by await, and cancellation is not a remote rollback.
API
| Function | Result | Purpose |
|---|---|---|
http.get(url) | owned_string | GET request without custom headers |
http.get_with_headers(url, headers) | owned_string | GET request with headers |
http.post(url, body) | owned_string | POST request with Content-Type: application/json |
http.post_with_headers(url, body, headers) | owned_string | POST with a custom header block |
http.request(method, url, body, headers) | owned_string | Synchronous request using GET, POST, PUT, PATCH, DELETE, or HEAD |
http.request_async(method, url, body, headers) | http_task | Queue a request without blocking on its response |
await task | owned_string | Wait for a queued task and return its response body |
http.cancel(task) | bool | Cancel a task that has not yet been awaited |
http.status() | int | HTTP status from the most recent request |
http.response_headers() | string | Final response headers, newline-separated |
http.last_error() | string | Transport/configuration error for the most recent request, or an empty string after a successful transfer |
Pass headers as one header per line, separated by \n, for example "Content-Type: application/json\nAuthorization: Bearer ...". Header names are validated and malformed headers are rejected. A task handle has the distinct http_task type, so the compiler rejects await or http.cancel on arbitrary integers. Await each task at most once. Cancellation frees its pending request state; using a consumed handle again reports an error/false result rather than reusing stale memory. Use http.status() to distinguish HTTP error responses such as 404 from transport failures: an HTTP 404 response is still a successful HTTP transfer and its body is returned. http.response_headers() returns the final response’s headers as newline-separated lines, replacing intermediate response headers after redirects. http.request(method, url, body, headers) accepts only the uppercase methods GET, POST, PUT, PATCH, DELETE, or HEAD. Its body argument is used for POST/PUT/PATCH/DELETE; GET and HEAD ignore it.
Responses are limited to 8 MiB. Connections time out after 5 seconds and whole requests after 15 seconds. Requests without custom headers automatically follow at most five redirects, restricted to HTTP/HTTPS. The built-in http.post JSON Content-Type header is permitted through redirects; if the header list includes any other header, automatic redirect following is disabled and the original 3xx status, body, and headers are returned. This avoids forwarding arbitrary credentials such as Authorization or X-API-Key to a redirect target. HTTPS certificate and hostname verification are enabled; the runtime does not offer an insecure TLS bypass. These safeguards help with reliability, but they do not make arbitrary URLs safe in every application or create a network sandbox.
Reading API keys from environment variables
Use env.get(name) to read a process environment variable. It returns an empty string if the variable is missing; it never invokes a shell. Do not hard-code API secrets into examples, source control, or the compiler.
function main() -> int {
let string api_key = env.get("MY_AI_API_KEY");
if (string.length(api_key) == 0) {
print("Set MY_AI_API_KEY before running this program.");
return 1;
}
let string headers = string.concat(
"Content-Type: application/json\nAuthorization: Bearer ",
api_key
);
let owned_string response = http.post_with_headers(
"https://api.example.com/v1/chat/completions",
"{\"model\":\"example-model\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}]}",
headers
);
print("HTTP status:", http.status());
if (string.length(http.last_error()) > 0) {
print("Request failed:", http.last_error());
return 1;
}
print(response);
return 0;
}
Replace the illustrative endpoint, model name, request JSON, and authorization scheme with those documented by the chosen provider. API response bodies are returned as strings. A dedicated JSON parser/serializer is not included in this milestone, so applications must currently interpret or construct JSON text themselves.
Error behavior
- Invalid URLs and malformed headers return an empty owned string, set
http.last_error(), and set status to zero. - DNS, TLS, timeout, connection, response-size, and other libcurl failures return an empty owned string and set
http.last_error(). - HTTP statuses such as 400, 401, 404, and 500 are not treated as transport errors. The response body and status are returned so the program can inspect them.
http.status()andhttp.last_error()describe the most recent HTTP operation in the current process; they are not per-request response objects.- Synchronous
http.get/http.requestcalls block until the transfer completes. Therequest_async/awaitworkflow allows several network transfers to progress concurrently, butawaititself blocks the current thread while pumping libcurl. It is not a general scheduler and must not be described as non-blocking on GUI event loops. General async functions, structured task groups, and GUI/event-loop integration remain future work. http.cancel(task)cancels a queued or in-progress request; cancellation is not a remote rollback and may happen after the server has received the request.
GUI and Game2D
The optional GUI runtime uses SDL2. The Game2D helper module is a small, experimental layer for learning and simple 2D prototypes—not a general-purpose game engine.
It provides helpers for window lifecycle, event polling, keyboard and mouse input, elapsed frame time, rectangle drawing, collision checks, and presentation. The repository includes a playable Dodge example. This chapter explains the setup and development workflow; the Game2D Library Reference and GUI API Contract define the exact API and its limitations.
Before you start
You need a working YLang compiler, a supported C toolchain, SDL2 development files, and `pkg-config`. Runtime testing also requires a graphical desktop session. The SDL2 runtime alone may not provide the headers and metadata needed to compile the example.
On Fedora, install the development dependencies with:
```sh sudo dnf install SDL2-devel pkgconf-pkg-config ```
On Debian or Ubuntu:
```sh sudo apt install libsdl2-dev pkg-config ```
On macOS with Homebrew:
```sh brew install sdl2 pkg-config ```
For Windows, follow the dedicated Windows Setup instructions; the native path has additional toolchain and packaging restrictions.
Build the example
From the repository root, run:
```sh make game2d-example ```
This builds the example as `build/dodge` on Linux and macOS, or `build/dodge.exe` on Windows. Run it from a graphical desktop session:
```sh ./build/dodge ```
On Windows, run `build/dodge.exe` from the appropriate MSYS2 environment.
If the build cannot find SDL2, check that its development files are installed and that `pkg-config` can locate them. If the program compiles but no window appears, confirm that you are running it in a graphical session rather than a headless terminal or CI environment.
What the frame loop does
A small real-time application typically repeats a few tasks:
- Poll input and window events so the application can respond to user actions.
- Update the program state, such as player position.
- Check collisions or other simple game rules.
- Draw the current frame.
- Present the frame and continue until the user closes the window.
Keep the update logic separate from drawing where practical. This makes the game easier to reason about and makes non-graphical logic easier to test.
Understand the limitations
Headless CI verifies type-checking, compilation, linking, and non-window helper behavior. It does not verify that a live window behaves correctly on every desktop, display server, or graphics driver. Test the game on a real graphical session before relying on it.
The library does not yet provide sprites, advanced animation, audio, a scene system, or a complete physics engine. Do not assume it is a replacement for a mature game engine. Check the API contract for supported functions and the exact behavior of window and event operations.
Troubleshooting
- SDL2 headers or libraries are missing: install the development package for your platform.
- `pkg-config` cannot find SDL2: check the package installation and environment paths.
- The example builds but no window appears: run it from an interactive graphical desktop session.
- A feature is missing: check the Game2D reference before assuming it exists.
- The behavior differs across operating systems: record the platform, compiler version, and smallest example, then compare the documented support levels.
Practice
Build and run the Dodge example. Identify where it reads input, updates game state, checks collisions, and draws the frame. Make one small change, rebuild, and test it in a live graphical session. Keep the change within the API described by the Game2D reference.
Platform Support
YLang has different support levels on each platform. Installation instructions do not imply the same backend coverage everywhere. The default C-backend workflow depends on GCC or Clang, while optional libraries and experimental backends add their own requirements.
Use this chapter as an overview, then read the linked platform guide and test the features your project actually uses.
Linux
Linux is a primary development platform for the C backend and the experimental NASM x86-64 subset. GCC and Clang are supported C compiler choices. A compatible Linux x86-64 system can also install the optional YSPM package; see YSPM Linux Packages.
For ordinary command-line programs, a C toolchain and Make are the main requirements. GUI/Game2D needs SDL2 development files and `pkg-config`. The NASM backend is experimental and supports a narrower set of programs than the C backend.
macOS
macOS is tested with Clang and GNU Make. Install Apple’s command-line developer tools and use Homebrew for GNU Make and optional SDL2 dependencies. Use `gmake` rather than the system BSD `make` when following repository commands.
GUI/Game2D programs need SDL2 development files and `pkg-config`. Verify the native toolchain and run the project’s tests in the environment where you intend to build your application.
Windows
The initial native path targets 64-bit Windows with MinGW-w64 from the MSYS2 MINGW64 environment. CI tests compilation, a native executable, relative imports, standard input, and SDL2 build/link. MSVC is not supported by this first milestone, and full Windows feature parity is not claimed.
Read the Windows Setup guide for toolchain commands and current restrictions. A program that builds on Linux may depend on an unsupported feature or library on Windows, so validate the subset your project uses rather than assuming complete portability.
Backend coverage is separate from OS support
The C backend is the default and broad-coverage route. The NASM backend is experimental and limited to a subset of the language on supported Linux x86-64 systems. A platform having a working compiler does not mean that every backend is available there.
Similarly, a source file passing `check` does not guarantee that it can be built by every backend. Use `build` for the default C route and only use the experimental assembly commands after checking the Backend Roadmap.
A practical portability checklist
Before claiming that an application supports a platform:
- Build it with the supported toolchain on that platform.
- Run the relevant unit and regression tests.
- Verify native dependencies and linking.
- Test runtime behavior, including file paths, console input, and failure cases.
- If it uses GUI/Game2D, run it in a real graphical session.
- Record known unsupported features instead of describing the application as fully portable.
Report a platform issue
Include the operating system and architecture, compiler version, selected backend, exact build command, full diagnostic output, and a minimal source file that reproduces the problem. This information helps distinguish a language issue from a platform dependency or backend limitation.
Editor Support
YLang source files use the .yl extension. Editor integrations are maintained
in the project repository under editors/.
Visual Studio Code
The VS Code extension provides syntax highlighting and language configuration,
and it can run the real ylang check command to report compiler diagnostics.
It also includes a terminal-based debugging command where the platform and
debugger support it. See the VS Code extension guide.
Neovim
The repository includes syntax highlighting and diagnostic-parser support for Neovim. The diagnostic integration depends on the installed YLang compiler. See the Neovim guide.
Zed
Initial Zed support is available as a development extension in
editors/zed. It uses Tree-sitter for syntax highlighting and configures
.yl files, line comments, bracket matching, and indentation.
To try it, open Zed’s Extensions view, choose Install Dev Extension, and
select the editors/zed directory from a local clone of the YLang repository.
The extension may need Rust and the WASI SDK to build its grammar.
This first version does not include compiler diagnostics, autocomplete, formatting, go-to-definition, or debugging in Zed. Those capabilities should not be assumed until a YLang language server or matching integration is implemented and tested.
Verify the language
Editor coloring is not a compiler check. Use the compiler itself to verify a program:
ylang check hello.yl
ylang build hello.yl -o hello
./hello
A file can be highlighted by an editor even when it contains invalid YLang. The compiler remains the source of truth for accepted syntax and semantics.
YLang Core Language Specification — 2.0.0
This document defines the stable core language contract for YLang 2.0.0. Named imports, structs, enums, GUI/Game2D APIs, the NASM backend, native Windows support, and YSPM packaging are documented separately and remain experimental; their behavior is not covered by the stable-core compatibility guarantee. Syntax not listed here is not part of the stable core contract.
Explicit conversions
YLang does not implicitly convert values between types. Use explicit conversion functions:
to_float(integer)converts a signed 64-bit integer to a double-precision float.to_int(float)truncates toward zero and terminates with a runtime error if the input is non-finite or outside the signed 64-bit range.to_string(value)supportsint,float,bool,char, andstring. A string input is returned unchanged.string.is_int(text)returns whether the entire string is a valid base-10 signed 64-bit integer.string.parse_int(text)converts a valid integer string; invalid or out-of-range input produces a runtime error. Check withstring.is_intfirst when parsing untrusted input.
1. Compilation model
YLang is a compiled language. ylang check parses and performs semantic checks. ylang build generates C and invokes GCC or Clang to produce a native executable. ylang emit-c writes C for inspection. There is no interpreter in this release.
2. Source form and statements
Statements end with ;, blocks use { and }, and comments use // through the end of a line or /* ... */ across lines. Block comments cannot be nested and must be closed. Keywords are lowercase and case-sensitive. Source files are text and embedded NUL bytes are rejected before lexing. Each source file passed to the CLI is compiled as one program. A program must define function main() -> int or function main() -> void, with no parameters.
3. Variables and constants
let int age;
let int score = 100;
let string name = "YLang";
let bool enabled = true;
let const int LIMIT = 100;
- Mutable declaration:
let type name;orlet type name = expression;. - Constant declaration:
let const type name = expression;. Constants must be initialized and cannot be reassigned. - A local variable must be assigned on every path before it is read. The checker rejects a read it cannot prove initialized.
- Global variables without explicit initializers receive a type-appropriate zero/empty default. Explicit initialization is preferred.
- A declaration can shadow a name in an outer block, but duplicate names within one scope are errors.
- Assignment is a statement, not a general nested expression.
4. Types
| Type | Meaning |
|---|---|
int | Signed 64-bit integer |
float | 64-bit floating-point value |
bool | true or false |
char | One byte; ASCII-oriented |
string | Immutable string data represented as a C string by the current backend |
void | Function return type only; cannot be used as a variable type |
No implicit numeric conversions are performed. Arithmetic operands must have compatible matching types. % is available only for int. Conditions must have type bool. Strings support equality and lexicographic ordering comparisons. String values are immutable.
User-defined enums (experimental)
Unit enums are supported as named, distinct types:
enum Direction { North, East, South, West }
function is_east(Direction direction) -> bool {
return direction == Direction.East;
}
function main() -> int {
let Direction facing = Direction.North;
if (is_east(Direction.East)) { print("east"); }
if (facing == Direction.North) { print("north"); }
return 0;
}
Declare an enum before using its type. Refer to variants with EnumName.Variant. Values of different enum types cannot be mixed, and a variant can be passed to or returned from a function with the same enum type. Variants currently have no payloads. A statement match (value) { EnumName.Variant -> { ... } } must contain exactly one arm for every variant; missing and duplicate arms are errors. For this milestone, the matched expression must not contain calls or assignments. Enum formatting with print, importing enum types across module boundaries, payload variants, and explicit numeric discriminants are not implemented yet. The C backend and the experimental NASM backend represent these unit variants as integer values while semantic analysis preserves their distinct YLang types.
5. Literals and strings
let int n = 42;
let float ratio = 1.25;
let bool ready = true;
let char initial = 'Y';
let string message = "Hello\n";
let string greeting = f"Hello {message}";
char is one byte, not a Unicode scalar. Integer literals support decimal notation and hexadecimal notation such as 0xFF; floating-point literals support decimal notation and scientific notation such as 1e3 or 2.5e-4. A string literal must be terminated. F-strings begin with f" and interpolate expressions inside {}. Supported interpolations include variables and operators. Function calls and assignments are not allowed inside interpolation expressions; compute the value in a preceding statement. Quoted string and character literals decode \n, \r, \t, \0, \\, \', \", \{, \}, \/, and \xNN. A hexadecimal escape consumes exactly two hexadecimal digits, so \x4142 means byte 0x41 followed by 42. Unknown or incomplete escapes drop the backslash and keep the following character; for example, \q becomes q. Character literals must decode to exactly one byte. F-strings apply the same escapes to literal text. Literal braces can also be escaped with doubled braces ({{ and }}).
6. Output
print(expression, ...) writes values separated by one space and appends a newline:
print("answer:", 42);
print(f"Count: {n}");
Supported printable values are int, float, bool, char, and string. print is a statement in this release.
7. Expressions and operators
- Unary:
-,not - Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,<=,>,>= - Boolean:
and,or - Assignment:
=and+=as statements; postfix++increments a mutable numeric variable by one - Grouping:
(expression) - Calls:
name(argument, ...)
Operator precedence is conventional: unary operators, multiplication/division/modulo, addition/subtraction, comparisons/equality, and, then or. Parentheses may be used to make grouping explicit. Evaluations with observable side effects are sequenced from left to right: function-call arguments, eager binary operands, struct initializer fields, and array initializer elements. For assignment, the target location is evaluated before the right-hand side. Boolean and and or short-circuit from left to right.
Signed integer overflow, integer division/modulo by zero, and floating-point division by zero are handled by generated runtime helpers and terminate the program with a runtime-error message and status 70. This does not mean every possible C-level issue is guarded or that the language is memory-safe.
Loops
Three loop forms are supported:
loop() {
// Repeat until break.
break;
}
while (count < limit) {
count += 1; or count++;
}
for (let int i = 0; i < limit; i = i + 1) {
print(i);
}
loop()is an unconditional loop.while (condition)repeats while a boolean condition is true.for (initializer; condition; increment)uses C-style clauses. The initializer may be aletdeclaration or expression; condition and increment may be omitted.break;exits the innermost loop andcontinue;starts its next iteration.- A
forinitializer declaration is scoped to the loop and its body.
Fixed-size arrays
YLang supports fixed-size arrays with an explicit element type and an initializer. The length is inferred from the initializer. Arrays may be local or global, must contain at least one element, and all elements must have the same type.
let int scores[] = [10, 20, 30];
function main() -> int {
scores[1] = 42;
print(scores[0], scores[1], scores[2]);
return 0;
}
- Indexing starts at zero.
- Array indices must be
int; every access performs a runtime bounds check and exits with status 70 on an invalid index. - Element assignment is supported, such as
scores[1] = 42;. - Whole-array assignment, returning arrays, nested arrays, and
constarrays are not supported yet. Array parameters usetype name[]; the generated native ABI passes a pointer and a separate length value. - Arrays must be initialized at declaration; their size cannot change at runtime.
Standard string and input library
YLang includes byte-oriented string helpers and a line reader for simple command-line programs:
let string name = io.read_line();
let string greeting = string.concat("Hello, ", name);
print(greeting);
print(string.length(name));
print(string.contains(name, "lang"));
print(string.starts_with(name, "Y"));
print(string.ends_with(name, "n"));
string.length(value)returns the number of bytes, not Unicode characters.string.contains(value, needle),string.starts_with(value, prefix), andstring.ends_with(value, suffix)returnbool.string.concat(left, right)returns a new string.string.replace(value, needle, replacement)returns a new string with every non-overlapping occurrence ofneedlereplaced. An emptyneedleleaves the original string unchanged.string.upper(value)andstring.lower(value)return case-converted copies using the C locale’s byte-oriented character mapping; they are not Unicode-aware.- Strings support lexicographic ordering with
<,<=,>, and>=, using bytewise C-string ordering. Equality remains case-sensitive. - The generated runtime tracks allocated strings until process exit, so repeated concatenation or case conversion in a long-running loop can increase memory use.
io.read_line()reads one line from standard input and removes its trailing newline. At end-of-file it returns an empty string.io.read_file(path)reads an entire UTF-8/ASCII-compatible text file into a string. A file-open/read failure or embedded NUL byte terminates the program with a runtime error; binary files are not supported by the string API.io.write_file(path, content)overwrites or creates a text file and returnstrueon success orfalseif it cannot open/write the file. It does not create missing parent directories.- File paths are interpreted relative to the process working directory. File I/O is synchronous and currently has no structured error/exception type.
path.exists(path)returns whether a path can be inspected withstat; it may return true for files, directories, or other filesystem objects.path.basename(path)returns the final path component, andpath.extension(path)returns the suffix after the final dot (without the dot); a leading dot alone is not treated as an extension. These helpers use POSIX path separators.image.open(path)launches the platform default opener (xdg-openon Linux,openon macOS) without invoking a shell and returns whether the launcher exits successfully. It requires a desktop session and a registered handler; it opens the file in an external application and does not decode or draw the image in a YLang window.
Experimental owned strings
The C backend has an experimental owned_string type. Unlike legacy string, an owned_string local owns its tracked heap allocation and is released automatically when its scope ends. This feature is not part of the stable 2.0.0 core compatibility contract and does not provide full Rust-style borrowing or lifetime checking.
let string text = "YLang";
let owned_string owner = string.own(text); // clones text into owned storage
let owned_string next = owned_string.concat(owner, "!");
let owned_string transferred = move(next); // next is unavailable until reinitialized
print(owner, transferred);
Use string.own(value) to clone borrowed text into owned storage. owned_string.concat, owned_string.replace, owned_string.upper, owned_string.lower, io.read_line_owned(), and io.read_file_owned(path) produce owned strings. Use move(name) to transfer one owner into another local, an owned-string function parameter, or an owned return value. A function that returns an owned value should return move(name) for a local binding. Implicit copying of an owned binding is rejected; use string.own(value) when a separate owned copy is required.
The compiler emits releases on normal scope exit and before return, break, and continue. Reassigning an owned variable releases its previous value. For allocation diagnostics, run the resulting program with YLANG_MEMORY_STATS=1; if all owned values have been released and the program created no other retained legacy strings, live tracked allocations and bytes should be zero at process exit.
The experimental borrowed_string type can be used for function parameters that read text without taking ownership:
function report(borrowed_string text) -> int {
print(text);
print(string.length(text));
return 0;
}
function main() -> int {
let owned_string message = string.own("hello");
report(message); // message remains owned by main
return 0;
}
A borrowed_string parameter is read-only. The type is rejected for local/global variables, struct fields, array parameters, and function return values. It does not free the passed value; the caller must keep the owner alive for the duration of the call. Passing an unbound owned_string temporary to a borrowed parameter is rejected. This is a limited immutable call-parameter view, not full lifetime inference or a general borrow checker.
Current limits: owned strings require the C backend. Global owned strings, arrays of owned strings, and owned-string struct fields are rejected until cleanup for those storage forms exists. Mutable borrows (&mut T), lifetime annotations, escaping borrows, and general alias checking are not implemented. Legacy string remains a borrowed C-string representation, and allocations produced by legacy string helpers may live until process exit. Do not treat this experimental feature as a security boundary or a claim of complete Rust-level memory safety.
Path and desktop integration
let string file = io.read_line();
if (path.exists(file)) {
print(path.basename(file), path.extension(file));
let bool opened = image.open(file);
if (not opened) { print("Could not launch the default image/file viewer."); }
}
The path namespace exposes exists, basename, and extension. The image.open(path) convenience API starts the operating system’s default file handler without passing the path through a shell, avoiding shell-metacharacter interpretation. This is OS integration rather than a built-in image decoder or a custom GUI image widget; native GUI bindings and in-process image decoding remain future work. These APIs currently target the POSIX systems supported by YLang.
Standard math library
YLang provides a built-in math namespace for common floating-point operations. These calls are checked by the compiler and emitted as native C math-library calls.
let float root = math.sqrt(81.0);
let float power = math.pow(2.0, 8.0);
let float angle = math.sin(0.0);
let float bounded = math.clamp(12.0, 0.0, 10.0);
Available functions (all arguments must be float, and all return float):
- Unary:
sqrt,sin,cos,tan,asin,acos,atan,sinh,cosh,tanh,exp,log,log10,floor,ceil,round,abs. - Binary:
pow,atan2,min,max,hypot. - Three arguments:
clamp(value, low, high); a lower bound greater than the upper bound triggers a runtime error.
YLang does not implicitly convert integers to floats, so use float literals such as 9.0. Domain errors and non-finite results follow the platform C math library behavior.
8. Conditions and loops
if (age >= 18) {
print("Adult");
} else if (age >= 13) {
print("Teenager");
} else {
print("Child");
}
loop() {
if (done) {
break;
}
continue;
}
Conditions must be boolean. loop() repeats indefinitely until break executes. while repeats while its condition is true, and for supports initializer, condition, and increment clauses. continue skips to the next iteration. break and continue are only valid inside a loop.
9. Functions
function add(int left, int right) -> int {
return left + right;
}
function main() -> int {
print(add(20, 22));
return 0;
}
Parameter and return types are explicit. Function overloading is not supported. A non-void function must return a value on supported control-flow paths. main has no parameters and returns int or void.
10. Modules and imports
A source file can import named functions or global variables from another .yl file:
from "./system_data.yl" import system_data, system_prefix;
function main() -> int {
print(system_data(), system_prefix);
return 0;
}
Import paths are resolved relative to the file containing the import, not the process working directory. Imports are transitive: an imported module may import names from another module. Every name used from another module must be imported explicitly. Functions and globals declared in a module are visible inside that module, but do not become visible to importers unless their names are imported. Different modules may define private helpers with the same name.
The compiler rejects unknown imported names, conflicting or repeated imports,
imports of the program entry point, missing module files, and circular imports.
The root source file must define main(); an imported module cannot define it.
Imports are currently supported by the default C backend. The experimental NASM
backend rejects multi-file imports explicitly until module-aware assembly
lowering is implemented.
11. Diagnostics and tooling
check reports parse and semantic/type diagnostics without invoking a native compiler. Diagnostics include an error code, file/line/column, source excerpt, caret, and a hint when one is available. build does not produce a successful executable if YLang diagnostics contain errors.
fix input.yl -o output.yl currently implements one high-confidence source transformation: changing a pritn(...) call typo to print(...) when no function named pritn is declared. It requires an output path and does not intentionally modify comment/string text. It is not a general repair engine. Always run check after applying a fix.
12. Unsupported features and implementation limits
The following are not supported by the current language subset: classes, methods on structs, inheritance, payload enums, generic Option<T>/Result<T, E>, exception syntax (try/catch), generics, raw pointers/references, returning arrays, nested/const arrays, and a dedicated LLVM/native backend. Typed structs and enums are implemented as experimental extensions described in the struct reference and the enum reference; they are not part of the stable-core compatibility guarantee. class, try, and catch may be tokenized as reserved words but are not valid executable constructs.
The compiler generates C, so native code inherits ordinary process privileges and is not sandboxed. Some formatted string values are stored until process exit; creating many such values in a long-running loop can increase memory consumption. The language does not define ownership/borrowing or a garbage collector in this release. Do not assume Rust-like memory-safety guarantees.
YLang 2.0 Module System — Proposal
Status: named local-file imports are implemented on the development branch; aliases and qualified module imports remain proposals. Treat this implementation as experimental until its CI matrix passes and the limitations below are resolved.
The compiler supports selective imports of top-level functions and globals using a local source path. It intentionally does not define a package registry or binary module ABI.
1. Current behavior and remaining limitations
A source file may import named declarations from another .yl file:
from "./string_tools.yl" import system_data;
The resolver loads transitive dependencies, resolves paths relative to the importing file, rejects cycles, and keeps each module’s declarations and imports separate during semantic analysis. Only explicitly imported names are visible to the importer; helper declarations remain private to their source module. Generated C function names are module-qualified to avoid collisions. Imported modules may not define main(), and the root program must provide the entry point.
Current limitations: import aliases and import "module.yl" as module are not implemented; there is no package manifest or registry. Quoted paths and dotted filenames are supported, and YLANG_PATH searches colon-separated library directories after the importing file’s directory. The NASM backend can now lower imported modules for its existing supported subset; unsupported imported features still produce explicit backend diagnostics, and NASM remains experimental.
2. Proposed syntax
Selective imports
Keep the existing spelling for compatibility:
from "math.yl" import clamp
from "text.yl" import trim, split
Optional empty parentheses after imported names may remain accepted during a deprecation period, but new examples should omit them because an import names a symbol, not a call.
Aliases
Use an explicit as clause:
from "text.yl" import trim as trim_text
function main() -> int {
let string cleaned = trim_text(" hello ");
print(cleaned);
return 0;
}
An alias changes only the local name. It must not rename the original declaration in its defining module. Duplicate local names in the same scope must produce a compiler diagnostic even if they originate from different modules.
Module-qualified imports
Allow importing a module under a local namespace name:
import "math.yl" as math
function main() -> int {
print(math.clamp(12, 0, 10));
return 0;
}
The exact punctuation can change during parser review, but the semantic rule should not: a qualified reference resolves only to an exported declaration in that specific module. It must not fall back to searching all flattened source files.
3. Name visibility rules
- A source file is one module, identified internally by its canonical resolved path for local imports.
- A module declaration belongs to that module only; it does not automatically become visible to the importing module.
- A selective import creates a local binding only for each named symbol, or its explicit alias.
- A qualified import creates one local module binding; declarations are accessed through that binding.
- A symbol not imported or declared in the current module is an unknown-name error, even if another reachable module happens to define it.
- A module may refer to its own declarations without importing itself.
- Until an explicit visibility modifier exists, declarations intended for import are exported by default. A future
privatemodifier may narrow visibility, but must not be silently inferred from filename or declaration order. - Duplicate local bindings are errors. Two modules may define the same function name without conflict when those functions are referenced through distinct module bindings.
- Cyclic imports remain rejected in the first stable milestone. Supporting cycles later would require a separate declaration/signature collection pass and clear initialization rules.
4. Resolution and compilation architecture
Do not implement namespaces by rewriting every source file into a single string and then relying on the global symbol table.
Use a staged architecture:
- Resolve: parse import directives and normalize paths relative to the importing file. Enforce current limits on module count, nesting, and total source size.
- Load once: keep one module record per canonical path and report path errors against the import site.
- Declare: collect top-level function and type signatures per module before analyzing bodies. This permits forward references within a module and gives a basis for future cycle support.
- Bind imports: build a module-local symbol table mapping local names to a defining module and declaration.
- Analyze: resolve every identifier using local declarations and explicit imports only. Report both the use site and, where useful, the conflicting binding.
- Lower/codegen: pass a resolved symbol identity (module ID plus declaration ID) to the backend. The C backend may mangle internal names to avoid linker collisions; this is an implementation detail, not language-visible syntax.
- Diagnostics: retain source spans for each original file so all errors point to the correct path and line.
The C backend can continue to compile the generated C as one translation unit initially, provided symbol identity and visibility have already been resolved correctly. Separate object files and a stable module ABI are later work, not prerequisites for correct source-level namespaces.
5. Diagnostics
Reserve stable diagnostics for module-specific failures. Proposed names are illustrative until added to the compiler’s diagnostic registry:
- malformed import syntax or invalid alias;
- file not found, unreadable file, or unsupported path;
- imported symbol does not exist in the specified module;
- name is not visible in the current module;
- duplicate local binding caused by two imports or an import/local declaration collision;
- cyclic import, including the full cycle chain where practical.
Diagnostics should name the importing file and line. When a symbol exists but is not imported, suggest the explicit import only when the candidate is unambiguous.
6. Required acceptance tests
The module system should not be marked stable until all of these pass in the ordinary test suite:
- Existing single-file programs behave identically.
- Selective imports work with one and multiple symbols.
- Two import directives can import different symbols from the same file without duplicate definitions.
- Quoted paths with spaces and paths relative to the importer work.
- Nested dependencies work independent of declaration order.
- Cycles fail with a stable diagnostic.
- A function present in a reachable module but not explicitly imported cannot be called.
- An imported symbol can be renamed with
as; the original name is not implicitly added to the local scope. - Two modules can define functions with the same name and calls to each qualified name reach the correct implementation.
- Duplicate aliases and alias-vs-local-name collisions fail before backend compilation.
- A symbol imported from module A is not automatically re-exported through module B unless re-export syntax is explicitly designed.
- Compile-fail tests check diagnostic codes and source locations.
- GCC, Clang, Windows, and sanitizers continue to pass where supported.
7. Migration plan
- Add resolver and semantic tests that describe the intended visibility rules.
- Introduce per-module declaration tables while retaining the existing syntax.
- Enforce selective-import visibility and reject accidental access to unimported declarations.
- Add selective-import aliases.
- Add qualified module imports and allow duplicate function names across modules.
- Update the language guide and examples only after parser, semantic analysis, and all supported backends agree.
Each step should be a separate reviewable change. Do not add package manifests, registry downloads, inheritance, or raw pointers as part of this module-system work.
8. Non-goals
This proposal does not promise a package manager, dependency versioning, binary compatibility across compiler releases, dynamic linking, cyclic module initialization, or a Rust-equivalent safety guarantee. Local-file imports should remain deterministic and should not execute arbitrary build scripts.
Structs (experimental)
YLang’s current struct milestone supports named fields with explicit types, positional construction, field reads, passing structs to functions, returning structs, and assignment between values of the same struct type.
struct Point {
int x;
int y;
}
function shift(Point p, int dx) -> Point {
return Point(p.x + dx, p.y);
}
function main() -> int {
let Point p = shift(Point(2, 7), 3);
print(p.x, ",", p.y);
return 0;
}
Current limits
- Declare a struct before using it; its fields must have valid non-void types.
- Construct with positional arguments in declaration order:
Point(2, 7). - Field reads and assignments are supported for named mutable struct variables, including chained access through nested structs, for example
p.x = 10;orhero.pos.x = 10;. The final field type is checked, and the root variable must be mutable. - No recursive by-value fields, methods, inheritance, generic structs, field defaults, or named constructor arguments.
- The C backend supports structs; NASM parity has not been established.
- This is experimental until the implementation PR and its CI matrix are reviewed and merged.
See YLang 2.0 design.
Enums and exhaustive matching (experimental)
YLang’s current enum milestone supports unit enums: named alternatives with no attached payload. Every enum declaration creates a distinct type, even when another enum has the same variants.
enum Direction { North, East, South, West }
function is_east(Direction direction) -> bool {
return direction == Direction.East;
}
function main() -> int {
let Direction facing = Direction.North;
match (facing) {
Direction.North -> { print("north"); }
Direction.East -> { print("east"); }
Direction.South -> { print("south"); }
Direction.West -> { print("west"); }
}
return 0;
}
Rules
- Declare the enum before using its type or variants.
- Write variants as
EnumName.Variant. - Enum values can be stored in variables, passed to functions, returned, and compared with
==and!=. - Assigning values from different enum types is a type error.
match (value)must include every variant exactly once. Missing variants and duplicate arms are errors.- Each arm uses
Variant -> statement; a block is recommended for clarity. - In this milestone, match values must be simple side-effect-free expressions; store a value in a variable before matching it.
- Enums are represented as integer tags in the C backend. Do not depend on the numeric tag values.
- Payload variants, generic enums, matching bindings, and NASM parity beyond the tested unit-enum subset are not promised as stable.
The syntax and semantics are experimental until the feature PR is reviewed and merged. See YLang 2.0 design for the remaining value-type roadmap.
YLang compiler error codes
YLang diagnostics include stable codes to help users search documentation and report bugs. Wording can improve between releases; documented meanings should remain stable.
Lexing and parsing
| Code | Meaning | What to do |
|---|---|---|
| E1003 | An expression was expected but the current token cannot start one. | Check the preceding operator and use a supported expression. |
| E1015 | print() has no arguments. | Pass at least one value, such as print("");. |
| E1016 | Unknown standard-library namespace. | Use a documented namespace such as math, string, io, path, or image. |
| E1017 | Expression nesting exceeds the parser’s supported depth. | Simplify deeply nested parentheses, unary operators, or call/index expressions. |
| E1020 | Malformed import declaration or path. | Use from "./module.yl" import name;. |
| E1022 | Imported source file could not be resolved or read. | Check the path relative to the importing file. |
| E1023 | Circular module dependency. | Break the import cycle. |
| E1024 | Imported function or global does not exist. | Check the target module declarations and spelling. |
| E1025 | Imported name is duplicated, ambiguous, or conflicts with a local declaration. | Import each name once and avoid local name conflicts. |
| E1026 | An imported module defines or imports the program entry point. | Keep main() in the root program file. |
Types, names, and expressions
| Code | Meaning | What to do |
|---|---|---|
| E2001 | Assignment or initializer type mismatch. | Match the declared type or use an explicit conversion. |
| E2002 | Unknown type name. | Use a declared or built-in type. |
| E2004 | Duplicate name in one scope. | Rename or remove the duplicate declaration. |
| E2005 | void used in a value position. | Use a value type, or remove the value where appropriate. |
| E2010 | Integer literal is outside the signed 64-bit range. | Use a representable integer. |
| E2011 | Floating-point literal is invalid or out of range. | Use a finite float literal. |
| E2012 | Character literal is not one byte. | Use one ASCII byte or an escape. |
| E2014 | Function calls or assignments appear in an f-string interpolation. | Compute the value before interpolating it. |
| E2020 | Unknown variable name. | Declare the variable or correct its spelling. |
| E2021 | Variable may be read before initialization. | Assign it on every control-flow path before reading it. |
| E2022 | Assignment to a const variable. | Keep constants immutable. |
| E2023 | Assignment used as a nested value expression. | Keep assignment as a statement. |
| E2027 | Invalid array operation or indexing target. | Index a supported array with an integer index. |
| E2029 | An array is used as a value without an index. | Index the array or pass it to a function parameter that explicitly accepts an array. |
| E2030 | Unary minus used with a non-numeric value. | Negate an int or float. |
| E2031 | Logical negation used with a non-boolean value. | Use a bool expression. |
| E2032 | Logical operators require boolean operands. | Use boolean expressions on both sides. |
| E2034 | Numeric operation uses incompatible operand types. | Convert explicitly so the numeric types match. |
| E2035 | Ordered comparison uses incompatible types. | Compare matching numbers, chars, or strings. |
| E2036 | Equality operands are incompatible or are unsupported struct values. | Compare matching comparable values; struct equality is not implemented. |
| E2040 | Unknown function name. | Check spelling or define the function. |
| E2041 | Wrong number of user-defined function arguments. | Match the function’s declared parameter count. |
| E2042 | Function argument type mismatch. | Match each argument to the expected type. |
| E2043 | Duplicate function definition in one module. | Keep one definition per function name in a module. |
| E2044 | A string standard-library function received a non-string argument. | Pass strings. |
| E2045 | An explicit conversion received an unsupported source type. | Check the conversion function’s accepted input type. |
| E2050 | print received a void expression. | Print a value, or change the function to return one. |
| E2051 | An if, while, or for condition is not boolean. | Use a boolean expression. |
| E2052 | break or continue appears outside a loop. | Place it inside a loop. |
| E2053 | A non-void function return omits a value. | Return a value of the declared type. |
| E2054 | A void function returns a value. | Use return; or change the return type. |
| E2055 | Function return type mismatch. | Return the declared type. |
| E2056 | Global initializer is not a constant expression. | Use a constant initializer or initialize inside a function. |
| E2057 | match does not receive a user-defined enum. | Match a declared enum value. |
| E2058 | A match arm uses a different enum type. | Use variants from the matched enum. |
| E2059 | Duplicate enum variant in a match. | Keep one arm per enum variant. |
| E2060 | Program has no main() function. | Define the program entry point. |
| E2061 | Invalid main() signature. | Use no parameters and return int or void. |
| E2062 | A non-void function path can end without returning. | Return a value on every supported path. |
| E2063 | Enum match is not exhaustive. | Add an arm for every variant. |
| E2064 | match input contains a call or assignment. | Store the value in a variable before matching. |
| E2070 | A struct declaration conflicts with an existing struct or enum name. | Choose a unique name for the struct. |
| E2071 | Duplicate struct field name. | Use a unique field name. |
| E2072 | Unsupported struct field type. | Use a field type supported by the experimental struct feature. |
| E2073 | Struct has no supported fields. | Add at least one supported field. |
| E2074 | Wrong number of struct constructor arguments. | Pass one value per field. |
| E2075 | Struct field initializer type mismatch. | Match arguments to declared field types. |
| E2076 | Member access target is not a struct. | Access a field on a value of a declared struct type. |
| E2077 | Unknown struct field. | Correct the field name or declaration. |
| E2078 | Unsupported struct assignment target. | Assign to a supported named struct variable’s field. |
| E2079 | Direct printing of struct values is unsupported. | Print individual fields. |
| E2080 | Unknown variant for an enum type. | Use a variant declared by that enum. |
| E2081 | Duplicate enum type name in a module. | Use a unique enum name. |
| E2082 | Duplicate variant in an enum declaration. | Declare each variant only once. |
| E2083 | Empty enum declaration. | Declare at least one variant. |
| E2084 | A match arm does not name an enum variant. | Write a valid enum variant in every arm. |
| E2085 | Wrong number of GUI function arguments. | Follow the GUI function’s documented signature. |
| E2086 | Conversion function received the wrong number of arguments. | Pass exactly one argument. |
| E2087 | length received the wrong number of arguments. | Pass one array argument. |
| E2088 | Standard-library function received the wrong number of arguments. | Follow the function’s documented signature. |
| E2089 | Unknown GUI function. | Check the GUI namespace API documentation. |
| E2090 | GUI function argument type mismatch. | Pass values of the types required by the GUI API. |
| E2091 | Direct printing of enum values is unsupported. | Compare variants and print a string label. |
| E2092 | Assignment target is unknown or unsupported. | Assign to a declared mutable variable, array element, or supported struct field. |
| E2093 | Increment operator has an invalid target. | Use ++ on a mutable numeric variable or supported struct field. |
| E2094 | Function parameter expects an array argument. | Pass a declared array variable of the expected element type. |
| E2095 | += target is not an int or float. | Use += only with numeric variables or fields. |
| E2096 | length received a non-array value. | Pass a declared array to length. |
| E2097 | Wrong number of math function arguments. | Match the math function’s documented arity. |
| E2098 | Math function argument is not a float. | Use a float literal or explicitly convert an integer with to_float. |
| E2099 | Function array argument has the wrong element type. | Pass an array whose element type matches the parameter. |
Runtime arithmetic and bounds failures currently use descriptive text rather than compiler error codes. Runtime source line and function attribution are not yet implemented.
YLang GUI API Contract (Proposal)
Status: implemented on main as an optional SDL2-backed binding surface. The API below describes the current first milestone; native GUI builds require SDL2 development files, while check and ordinary non-GUI builds do not.
This document defines a deliberately small first binding milestone. The contract must be agreed before changing the parser, semantic checker, C generator, and native build driver together.
Goals
- Make a small graphical window usable from ordinary YLang source.
- Preserve the current default build: programs without
gui.*calls must not need SDL2. - Keep
checkindependent of SDL2 installation; dependency discovery belongs to nativebuild. - Validate argument count and types before code generation.
- Keep the API portable across the project’s supported Linux and macOS targets.
- Make unsupported calls fail with YLang diagnostics rather than producing broken C.
Proposed first API
The first milestone intentionally supports one active window per process. It does not expose raw pointers or arbitrary C interop.
| Call | Return type | Contract |
|---|---|---|
gui.init() | bool | Initialize the GUI runtime; false means initialization failed. |
gui.create_window(title, width, height) | bool | Create one window; title is string, dimensions are positive int values. |
gui.poll() | bool | Process pending events; false means the window was closed or polling failed. |
gui.key_down(key) | bool | Check a named key after polling; supports SDL scancode names such as Left, Space, and Escape. |
gui.mouse_x(), gui.mouse_y() | int | Read the mouse position in window coordinates. |
gui.mouse_button_down(button) | bool | Check mouse button state (1 left, 2 middle, 3 right; values 1–5 are accepted). |
gui.ticks_ms() | int | Read SDL’s monotonic timer in milliseconds after initialization. |
gui.clear(red, green, blue) | bool | Clear the current frame using RGB channels in the range 0–255. |
gui.draw_rect(x, y, width, height, red, green, blue) | bool | Draw a filled rectangle; dimensions must be positive and color channels 0–255. |
gui.present() | bool | Present the current frame. |
gui.last_error() | string | Return a readable description of the latest GUI runtime error. |
gui.close() | void | Destroy the active window and release its resources. Safe to call after failed creation. |
gui.shutdown() | void | Release initialized GUI runtime resources. |
All calls except gui.close() and gui.shutdown() require successful initialization and, where applicable, a created window. Runtime failures are returned as false; the latest human-readable explanation is available through gui.last_error(). No implicit numeric conversions are allowed.
Example target syntax
This example shows the intended use of the API and is included in the implementation work.
function main() -> int {
if (not gui.init()) {
print(gui.last_error());
return 1;
}
if (not gui.create_window("YLang GUI", 800, 500)) {
print(gui.last_error());
gui.shutdown();
return 1;
}
loop() {
if (not gui.poll()) { break; }
if (not gui.clear(24, 28, 38)) { break; }
if (not gui.draw_rect(80, 80, 240, 120, 22, 160, 180)) { break; }
if (not gui.present()) { break; }
}
gui.close();
gui.shutdown();
return 0;
}
The example is available as examples/gui.yl. The regression suite checks its types and generated C; an SDL2 runtime smoke test runs when the development package is available.
Compiler and build requirements
- Parser: accept
guias a recognized standard-library namespace using the same qualified-call representation as existing namespaces. - Semantic analysis: define an explicit signature table for every supported call; check arity and each argument’s type; assign the documented return type. Unknown
gui.*names must produce a stable diagnostic. - C generation: emit calls to a narrow adapter layer. Do not pass arbitrary user-selected C symbols through to generated code.
- Native build driver: detect whether the checked program uses GUI calls. Only then discover SDL2 compiler/linker flags and link the optional runtime. Use argument-vector process execution, not a shell command string.
- Portability: account for the repository-relative runtime source and headers, and report a clear build error if SDL2 development files are missing. The ordinary non-GUI build must continue working without SDL2.
- Experimental NASM backend: reject GUI calls with a clear unsupported-feature diagnostic; do not silently generate invalid assembly.
- Documentation: clearly label this as an optional SDL2 feature and distinguish compile-time checking from dependency-required native building.
Acceptance tests
checkaccepts a well-typed GUI program even on a machine without SDL2 installed.checkrejects incorrect arity, wrong argument types, unknown GUI names, and invalid types with source-located diagnostics.- A regular non-GUI program builds and runs with no SDL2 dependency.
- A GUI program builds on Linux and macOS when SDL2 development files are installed.
- A GUI build with missing SDL2 reports the required package/tooling and exits nonzero.
- Runtime initialization/window-creation failure is reportable through
gui.last_error(). - The existing C GUI example and all compiler regression tests remain passing.
- The NASM backend rejects GUI programs before assembly generation.
Deliberate non-goals for the first milestone
No widget toolkit, text input, images, audio, a reusable game-loop abstraction, multiple-window handles, custom event objects, arbitrary FFI, or full Pygame-like game framework. Those should build on the first API only after its ownership, error, and lifecycle behavior is tested.
YLang Compiler Architecture
YLang 2.0.0 uses a conventional multi-stage compiler pipeline. The stable core compiles through generated C and GCC/Clang; named imports, structs, enums, GUI/Game2D, and the NASM backend are explicitly experimental extensions.
- Lexer (
src/lexer.c) — turns source text into positioned tokens. - Parser (
src/parser.c) — builds the program representation and calls shared diagnostics when syntax is invalid. - Frontend utilities —
src/arena.cowns arena allocation and arena-backed vectors;src/strbuf.cowns generated-text buffers;src/diagnostics.cformats source-located errors and warnings. - Semantic analysis (
src/sema.c) — resolves names, validates initialization and constant assignment, and checks types. - Module loader (
src/module.c) — recursively loads imported files, detects cycles, resolves named imports, and merges module ASTs while retaining per-module visibility. - C code generator (
src/codegen.c) — writes C for the checked program, including runtime helpers for supported checked arithmetic operations. - Driver (
src/driver.c) — orchestrates module loading and checking, writes generated C, and invokes GCC or Clang without going through a shell. - CLI (
src/main.c) — parses user commands and their options.
The internal compiler model is declared in src/internal.h; the public-facing compiler and token declarations are in include/ylang/.
Experimental NASM backend (Linux x86-64)
The optional emit-asm command writes NASM syntax for a deliberately limited subset of YLang. Unlike the default C backend, the generated program uses the Linux x86-64 system-call ABI for output and process exit, so a generated program can be assembled with NASM and linked directly with ld without GCC/Clang or a C runtime:
./build/ylang emit-asm tests/asm-basic.yl -o build/asm-basic.asm
nasm -f elf64 build/asm-basic.asm -o build/asm-basic.o
ld -o build/asm-basic build/asm-basic.o
./build/asm-basic
For the same steps in one command, use build-asm:
./build/ylang build-asm tests/asm-basic.yl -o build/asm-basic
./build/asm-basic
The initial subset supports scalar int, bool, char, and string values, local variables, up to six scalar function parameters, user-defined function calls, integer arithmetic and comparisons, if/else, loops, break, continue, return, and print. Integer overflow and invalid integer division terminate the program with status 70.
The experimental backend currently rejects global declarations, arrays, floats, f-strings, string comparisons, and/or, standard-library or conversion calls, and multi-file imports. The standard build command continues to use the established C backend. The separate build-asm command runs NASM and ld for supported programs; emit-asm writes the assembly source for inspection. The experimental backend is Linux x86-64 specific and is not supported on macOS or other architectures.
Diagnostic policy
Compiler errors are intended to be stable, user-facing output. Changes should preserve useful codes where practical and include regression tests for source locations and hints. A check failure must not be reported as a native build failure. Code generation is only entered after parsing and semantic analysis report no errors.
Native backend
The v1.0 backend generates C17 and invokes the selected native compiler (gcc by default or clang when selected). Linux builds use GNU linker RELRO/NOW hardening and _FORTIFY_SOURCE; macOS builds omit GNU/Linux-specific flags and use the platform linker. Generated programs are ordinary native executables, not sandboxed. For native builds, generated C is placed in a private temporary directory and removed after the native compiler exits. This avoids clobbering a project file and avoids collisions between concurrent build invocations. emit-c writes to the requested path, or build/ylang-generated.c if no output path is given.
Scope
This architecture documents the current implementation, not a promise of features outside docs/language-spec.md. Arrays, pointers/references, classes, exception syntax, and a finalized memory model are intentionally outside the original 1.0.0 feature set. Named imports are supported by the default C backend; the NASM backend remains a limited experimental subset.
Backend strategy and roadmap
Current state
- YLang’s compiler front end and type checker are written in C17.
- The default
ylang buildbackend emits C, then invokes GCC or Clang to produce an executable. This is the broadly supported path for the beta. - The experimental NASM backend emits Linux x86-64 assembly for a restricted subset and links with NASM plus
ld. It does not support every feature accepted by the C backend. - Programs requiring the optional SDL2 runtime need the documented native dependencies; the NASM backend rejects GUI calls explicitly.
This means YLang programs do not yet have a complete, general-purpose native backend independent of GCC/Clang. The NASM subset is not a drop-in replacement for the C backend.
Design principles
- One front end. Parsing, name resolution, type checking, and diagnostics stay shared. Do not implement language semantics separately inside each backend.
- Typed intermediate representation first. Before expanding the NASM subset or starting another backend, introduce a documented, backend-neutral representation that preserves resolved types, control flow, imports, and source locations.
- Declare capabilities. Each backend must have a tested capability matrix. Unsupported language constructs must cause explicit diagnostics before emission; never silently emit a different behavior or fall back without telling the user.
- C remains the reference path for broad coverage. The C backend stays the default until an alternative covers the required language subset and passes the same behavior tests.
- Differential testing is mandatory. For every construct supported by more than one backend, compile the same source with each backend and compare stdout, stderr/runtime-error classification, and exit status. Keep small regression fixtures for arithmetic, control flow, enum matching, and imports.
- Fuzz the shared front end. Keep lexer/parser unit tests and run the libFuzzer harness under AddressSanitizer and UndefinedBehaviorSanitizer in CI where toolchain support is available.
Proposed milestones
B0 — 2.0 beta
- Keep C/GCC/Clang as the default supported build path.
- Keep NASM explicitly experimental and limited to its documented Linux x86-64 subset.
- Run differential parity tests on the common subset; report skips on unsupported platforms instead of implying parity was tested.
B1 — Backend-neutral typed IR
- Specify expression, statement, type, control-flow, module/import, and source-location representations.
- Add IR invariants and tests independent of any target backend.
- Move code generation gradually behind a backend interface without duplicating semantic checks.
B2 — Native backend proof of concept
Evaluate two options with a small, measurable prototype:
- LLVM IR/object output, for mature optimization and target support, while explicitly accounting for LLVM toolchain size and deployment costs.
- Direct machine-code or assembly generation, if a small dependency footprint is more important, with target-specific code isolated from the language semantics.
The decision should be based on executable correctness, portability, debug information, compile time, binary size, maintenance cost, and cross-backend test coverage—not on a target date or a single benchmark.
B3 — Expand only behind parity gates
- Extend a backend only when the IR and tests express the feature fully.
- Require parity fixtures for the feature’s supported subset and compile-fail diagnostics for unsupported combinations.
- Keep runtime/standard-library dependencies explicit and versioned.
B4 — Reassess the GCC/Clang requirement
Only advertise a general independent backend after it can build and run the supported language suite on every claimed target from a clean checkout. Building YLang’s compiler itself may still require a C toolchain unless a separate bootstrap path is implemented.
Out of scope
This is a roadmap, not a promise that LLVM, a direct machine-code backend, Windows support, or a self-hosting compiler will ship in the 2.0 beta. Do not delay the beta for those future milestones, but do not imply that the current NASM subset already removes the C compiler requirement for general YLang programs.
YLang Debugger
Status: experimental 2.2 development feature. The CLI can create a debug build and launch the system-native debugger. This is not yet a complete YLang Debug Adapter Protocol implementation, and it does not claim full debugger support on Windows or across both code-generation backends.
Requirements
- Linux: GCC or Clang and GDB.
- macOS: Clang and LLDB from Xcode Command Line Tools.
- Windows: the interactive
ylang debugcommand is not implemented yet.
Start a debug session
From the repository root:
make
./build/ylang debug examples/hello.yl -o build/hello-debug
The command performs the normal semantic checks, generates C with YLang source-line directives, compiles with -O0, full debug symbols, and frame pointers, then opens GDB (Linux) or LLDB (macOS). The ordinary ylang build optimization settings are unchanged.
GDB commands
At the GDB prompt, set a breakpoint in the YLang source, then run the program:
break examples/hello.yl:3
run
next
backtrace
info locals
continue
quit
Use a path matching the path shown in the build command. Imported-module statements carry their own source path when the compiler can identify the originating module.
LLDB commands
breakpoint set --file hello.yl --line 3
run
next
bt
frame variable
continue
quit
Adjust the source filename and line for the program being debugged.
Current limitations
- The native debugger is operating on the compiler’s generated C program with YLang source line markers. This improves source locations and breakpoints, but it is not the same as a dedicated YLang debug adapter.
- Locals may use generated C names; scope and optimized-out values may not always resemble a higher-level language debugger.
- The generated program uses the C backend. The NASM backend has no matching source-level debug contract.
- The VS Code command starts an interactive debugger in the integrated terminal. Breakpoint gutter integration, a dedicated Variables/Call Stack UI, and Debug Adapter Protocol support are future work.
- The Windows interactive debugger path is not implemented. Do not infer macOS/Windows parity from Linux test results.
- Debugging executes the target with the current user’s privileges; it is not a sandbox.
Report failures with the platform, GDB/LLDB version, exact command, a small .yl reproducer, and the output.
Reproducible Performance Measurements
Status: development tooling. Numbers are machine-specific and should not be treated as release guarantees.
Run the native benchmark
Build YLang first, then run the same workload on the same host and toolchain before and after a change:
python3 benchmarks/bench-native.py benchmarks/native-loop.yl \
--cc gcc --runs 30 --warmup 3 \
--expected-file benchmarks/expected-native-loop.txt
Use --cc clang for the Clang path. The optional --compiler /path/to/ylang chooses the compiler executable. Add --keep-dir build/benchmark-artifacts to preserve the final generated executable for inspection.
The report shows the YLang version, host platform, selected native C compiler, compile-time median and P95, execution-time median and P95, and executable byte size. The expected-output check ensures that the benchmark is still measuring a program with known behavior rather than a broken output path.
What is measured
- Compile time includes the YLang invocation, semantic checking, C generation, and native compiler/linker run.
- Run time includes starting the executable and its exit; stdout/stderr capture and process creation are part of the measurement. This is not a pure instruction-cycle benchmark.
- Binary size is the size of the final executable produced by the last measured build.
- Warm-up iterations are not included in the reported samples. P95 is the nearest-rank 95th percentile.
The CI smoke test verifies that the harness executes and reports its metrics; it intentionally does not enforce a universal speed target. Do not compare measurements from different machines, native compiler versions, optimization settings, power modes, or workloads as if they were a controlled experiment.
Suggested comparison record
For performance-sensitive changes, keep the commit SHA, YLang version, operating system/architecture, native compiler version, YLANG_OPT_LEVEL, run count, warm-up count, median, P95, binary size, and expected program output together. Re-run several times if a result is close to the noise floor. Prefer stable improvements across repeated measurements over a single faster number.
Security Model and Safe Defaults
YLang is a compiled toolchain, not a sandbox. A YLang executable runs with the privileges of the user who starts it. The compiler does not isolate file access, processes, network access, or imported native code.
HTTP/HTTPS client
The experimental C-backend client uses libcurl and applies these defaults:
- Only
http://andhttps://URLs are accepted. TLS certificate and hostname verification remain enabled; there is no insecure verification bypass. - Connection attempts time out after 5 seconds and the entire request after 15 seconds.
- Response bodies are capped at 8 MiB, and request/response header text is bounded to 16 KiB.
- Automatic redirects are limited to five and allowed only for HTTP/HTTPS. The built-in JSON
Content-Typeheader is permitted through redirects, but if the request includes any other header, a 3xx response is returned directly rather than forwarding arbitrary headers such asX-API-Keyto a new target. - HTTP status codes such as 401, 404, and 500 are response statuses, not transport errors. Check
http.status()andhttp.last_error()separately. - At most 32 HTTP task handles may be in flight.
http_taskvalues are process-local, single-consumption handles; cancellation does not undo a request already received by the server.
These safeguards reduce common accidental leaks and resource exhaustion, but they do not make a caller-supplied URL safe in every deployment. In server-side applications, validate URLs against an application-specific hostname/IP allow-list and consider access to loopback, private, link-local, and metadata addresses before making a request.
API keys and environment variables
Use env.get("SERVICE_API_KEY") to read a credential supplied by the parent process. Missing variables return an empty string. The helper does not invoke a shell and does not grant additional permissions.
Do not commit real API keys, print them in diagnostic output, or include them in generated source files. Use the credential mechanism supported by the host or CI environment, restrict keys to the minimum permissions required, and rotate leaked credentials.
Native execution and debugging
ylang build,ylang debug, and the native executable run code on the host with the invoking user’s normal permissions.- Debugging is not a privilege boundary or sandbox. GDB/LLDB can inspect process state according to the user’s operating-system permissions.
- The experimental debugger is implemented for the C backend on Linux (GDB) and macOS (LLDB). Windows interactive debugging and the NASM source-debugging path are not implemented.
- Generated C and compiler diagnostics are developer tools, not a guarantee that arbitrary programs are memory-safe. YLang does not claim Rust-level memory safety; continue running regression tests, sanitizers, and fuzzing on supported environments.
For a security report, include the affected commit, OS/toolchain, exact compiler command, minimal reproducible source, and whether the issue reproduces with a non-privileged account. Do not post live credentials or private user data in an issue.
YLang 2.2.0 Release Scope and Follow-up Roadmap
Status: YLang 2.2.0 is released. This document records the shipped scope and follow-up work; incomplete goals below are not claims that those features shipped in 2.2.0.
YLang 2.2.0 expands the toolchain with an initial HTTP/async-task API, environment-variable access, source-aware command-line debugging, and developer-tooling improvements. The documented core language and default C-backend workflow remain the compatibility foundation; the new APIs and integrations retain their stated experimental limits.
Scope: five feature areas
1. Asynchronous programming
Provide a deliberate async model for work that waits on I/O, especially HTTP requests.
Acceptance goals:
- Document the execution model before freezing syntax: scheduling, suspension/resumption, cancellation, and shutdown behavior must be explicit.
- Define how errors and return values move through asynchronous calls.
- Ensure blocking operations are not advertised as non-blocking.
- Add tests for successful completion, failures, timeouts, cancellation, and repeated use.
- Preserve existing synchronous programs and the default build path.
YLang 2.2.0 ships a first async I/O slice: http.request_async(...) returns an http_task, await task consumes it to return the response body, and http.cancel(task) requests cancellation. It uses libcurl’s multi interface so queued transfers progress concurrently while a task is awaited. General-purpose async functions, a background scheduler, and GUI/event-loop integration remain unimplemented; the broader acceptance goal is only partially complete.
2. HTTP and API requests
Provide a standard, typed way to call HTTP APIs from YLang. A first useful milestone should cover:
- GET and POST, with a clear path to other common methods.
- Request headers, request bodies, response status, response headers, and response bodies.
- JSON request/response interoperability.
- Connection and overall timeouts, bounded response sizes, and useful error categories.
- HTTPS certificate verification enabled by default.
- Documented redirect behavior.
- Examples that read credentials from environment/configuration rather than hard-coding secrets.
YLang 2.2.0 implementation status: the C backend has synchronous HTTP helpers and a generic method API; http.request_async, await http_task, and http.cancel use libcurl’s multi interface. Response bodies remain strings and there is no built-in JSON parser/serializer. The NASM backend does not support HTTP; the native Windows HTTP path is covered by CI, but interactive debugging is not implemented there. Never embed API credentials in the compiler or examples.
3. YLang Debugger
Build source-level debugging around YLang files rather than asking users to debug generated C manually.
Initial capability goals:
- Debug build mode with symbols and settings suitable for stepping.
- Breakpoints, continue, step over, step into, and step out.
- Show locations in the original
.ylsource and display a call stack. - Inspect local variables where generated-code mapping supports it.
- Report a clear diagnostic when a value or source mapping cannot be inspected.
- Integrate debugging into the YLang VS Code extension.
- Start with the established C backend; do not imply NASM support until debug information and behavior are tested.
YLang 2.2.0 implementation status: ylang debug creates an unoptimized debug build with symbols and source-line mapping, then launches GDB on Linux or LLDB on macOS. VS Code exposes a terminal-based command for it. There is no dedicated Debug Adapter Protocol adapter, so IDE breakpoints, Variables/Call Stack panes, generated local names, and scope mapping are still limited. The NASM backend and Windows interactive debugging are not supported.
4. Performance and security improvements
Treat speed and security as measurable engineering work, not an unqualified marketing guarantee.
Acceptance goals:
- Keep representative benchmarks for compiler time, executable runtime, and binary size.
- Compare changes against a recorded baseline on the same machine and toolchain.
- Add regression tests for arithmetic overflow, array bounds, error paths, ownership cleanup, and backend parity as appropriate.
- Run the existing test suite and sanitizers; extend front-end fuzzing where practical.
- Review process invocation, file handling, network behavior, and secret handling for unsafe defaults.
- Publish only reproducible results and document remaining memory-safety limitations.
5. VS Code onboarding and developer experience
Make it straightforward to discover, install, and use the official YLang extension.
Acceptance goals:
- Publish and maintain the extension on the Visual Studio Marketplace with the
.yllanguage contribution and clear installation instructions. - Verify that VS Code can recommend the Marketplace extension when opening an unrecognized
.ylfile. Dynamic file-type recommendations depend on the extension being available from the Marketplace. - Keep the extension ID stable and document manual installation for offline users.
- Provide clear compiler-path setup, diagnostics, run/check commands, and debugger integration.
- Test the extension package and supported VS Code versions before release.
A workspace recommendation helps people who open the YLang repository, but does not replace Marketplace discovery when someone opens an arbitrary .yl file outside that repository.
Brand and website refresh
The new YLang identity and redesigned official website are companion launch work, not a sixth language feature. Keep the language name and .yl identity recognizable while introducing consistent logo, typography, colors, icon assets, installation entry points, and documentation navigation.
Website acceptance goals:
- The home page, book, editor extension, and repository use consistent approved brand assets.
- The getting-started path makes compiler and editor setup easy to find.
- Existing technical guidance remains accurate; styling changes must not silently remove or rewrite documentation.
- Links, navigation, mobile layouts, and code examples are checked before launch.
Follow-up priorities
- Design and implement general-purpose async functions, scheduler behavior, and cancellation/error propagation.
- Add a documented JSON parser/serializer and improve HTTP API ergonomics.
- Evaluate a dedicated VS Code Debug Adapter Protocol integration, local-variable inspection, and expanded platform support.
- Continue performance and security testing with reproducible baselines and explicit compatibility checks.
- Complete Marketplace publication and future editor-discovery work separately from the compiler release.
Future release gates
For future releases that expand these areas:
- Require test, sanitizer, and relevant platform CI jobs on the exact release commit.
- Add reference documentation and runnable examples for new syntax and standard-library APIs.
- Preserve the documented core compatibility policy.
- Test HTTP security defaults, timeout/error behavior, and debugger platform boundaries.
- Verify editor packaging, Marketplace instructions, website links, assets, and installation guidance before claiming those tasks complete.
Current editor/website status: VS Code is version 0.6.0 in this release, the repository recommends it via .vscode/extensions.json, syntax highlighting includes http and env, and the debug command is wired to the native compiler. Marketplace publication is still a manual release task; a workspace recommendation does not discover the extension for arbitrary .yl files. The homepage, reference landing page, and online book share the refreshed dark-teal/rust design, while the original static home is preserved as classic.html in the deployment artifact.
The feature list and order may change based on implementation findings. The HTTP task API is only the first async slice; do not mark broader asynchronous programming complete until general async functions, cancellation/error behavior, and event-loop integration have been separately designed and tested. Do not describe a future feature as implemented until its acceptance checks pass.
YLang Debugger Design
Status: design proposal for the YLang 2.2 roadmap. No debugger command or VS Code debug configuration should be advertised as implemented until this specification is accepted and the implementation passes its tests.
Goal
Let a developer debug a .yl file from VS Code or the command line, seeing YLang source locations and meaningful variable names rather than generated C internals.
Initial scope
The first implementation should target the established C backend. Support for the NASM backend is out of scope until it can emit and verify appropriate debug information.
The first milestone should aim to provide:
- Build and launch a YLang program with debug symbols.
- Set source-line breakpoints, continue, step over, step into, and step out.
- Show the current YLang source location and call stack.
- Inspect supported local scalar variables.
- Display clear errors when the compiler, native debugger, symbols, or source mapping are unavailable.
Inspection of arrays, structs, owned values, optimized code, and generated runtime internals should be added only when each representation can be exposed reliably.
Compiler and source mapping
- Add an explicit debug build mode; do not silently change the normal optimized build.
- Compile generated C with debug symbols and debugging-friendly optimization settings.
- Preserve a mapping between YLang source files/lines and generated C. A prototype should evaluate generated-C #line directives and any additional source-map metadata needed for expressions or locals.
- Preserve source file identity for relative imports and files outside the current working directory.
- Keep temporary generated sources and binaries in a predictable, documented location, and make cleanup behavior explicit.
- Do not rely on C variable names alone as the permanent YLang debugger interface.
A source-level breakpoint must land on a valid executable statement. If a line contains only a declaration, comment, or compound construct with no instruction location, the adapter should explain or resolve the breakpoint rather than claiming that it was bound exactly.
Debug-engine choice
Before settling the public interface, implement a small proof of concept and compare:
- Reuse the VS Code C/C++ debugger: generate appropriate launch configuration and source mapping, with a clearly declared dependency on the C/C++ extension.
- Provide a YLang debug adapter: implement the Debug Adapter Protocol and communicate with a supported native debugger such as GDB/LLDB.
Choose based on reliable YLang source stepping, local-variable inspection, cross-platform behavior, installation complexity, and maintenance cost. Do not commit the extension’s public debugger type until the prototype shows that generated-C details can be hidden adequately. Never invoke a shell command assembled from user-controlled source paths or program arguments.
VS Code user experience
The desired flow is:
- Open a YLang workspace and select a YLang debug configuration.
- Press F5; the extension checks required tools and builds a debug executable if necessary.
- Set breakpoints in the .yl editor and use the usual Run and Debug controls.
- See the call stack, supported locals, and console output in the normal VS Code panes.
- Receive actionable setup diagnostics if GCC/Clang or the native debugging engine is missing.
The extension must not automatically install or execute an untrusted debugger or compiler. Tool installation remains an explicit user action.
Runtime safety and correctness
- Debugging is not a security sandbox; the program still runs with the user’s normal permissions.
- Use argument-vector process APIs and validate executable paths.
- Do not change runtime semantics to make a variable appear inspectable.
- Keep ordinary builds independent from debugger dependencies.
- Preserve program arguments, environment variables, working directory, stdin/stdout, exit status, and termination behavior.
- Separate compiler errors from debugger startup errors and target-program failures.
Acceptance tests
Use small YLang programs with known output and state to verify:
- Breakpoint binding to a specific source line.
- Continue, step-over, step-into, and step-out behavior.
- Correct source display for a function in an imported module.
- Call stack names and lines for nested YLang functions.
- Values of supported local integers, floats, booleans, and strings at a breakpoint.
- Handling of optimized-out/unavailable values without fabricated output.
- Missing debugger, invalid source, build failure, early exit, and user-requested termination.
- Paths with spaces and non-default working directories.
- Existing release and sanitizer tests remain passing.
Platform support
Document debugger-engine requirements per platform. The initial support claim must match what CI and local integration tests actually exercise; a successful Linux prototype is not evidence of Windows or macOS parity.
Option and Result design for YLang 2.0
Status: design proposal only. This document does not claim that Option or Result are implemented.
Goals
- Represent absence and recoverable failure without magic sentinel values.
- Keep YLang’s explicit typing rule: no implicit conversion between
int,float,string, or other types. - Make failure paths visible to the type checker.
- Keep the stable 1.1.x language behavior unchanged until the feature is implemented and tested.
Recommended first implementation: tagged values, not generics
The current type system does not yet provide general type parameters. Do not pretend that Option<T> or Result<T, E> work until generic types, payload layout, and code generation are implemented.
For an incremental milestone, introduce compiler-internal tagged values with concrete payload types, then generalize only after the type representation can express type parameters safely. The surface syntax should be finalized before code changes.
Conceptual semantics:
Option<T>: eitherSome(value)orNone.Result<T, E>: eitherOk(value)orErr(error).- Matching must be exhaustive, like the existing unit-enum
matchchecks. - Accessing a payload is legal only after the checker proves which variant is active.
- No implicit conversion is performed for payloads or errors.
Proposed user-facing examples (illustrative, not currently compilable)
// Illustrative syntax only; do not use as a working example yet.
let Option<int> maybe_port = Some(8080);
match maybe_port {
Some(port) => { print(port); }
None => { print("no port"); }
}
// Illustrative syntax only; do not use as a working example yet.
let Result<int, string> parsed = parse_port("8080");
match parsed {
Ok(port) => { print(port); }
Err(message) => { print(message); }
}
Implementation sequence
- Type representation: define type identity for payload-bearing variants and ensure incompatible payload types cannot be mixed.
- Parser and AST: parse generic type arguments and variant payload patterns without changing existing enum parsing.
- Semantic analysis: validate constructors, payload types, duplicate arms, exhaustive matching, and payload binding scope.
- C backend: emit a tagged representation with explicit payload storage and safe access only in matching arms.
- Assembly backend: keep the feature disabled for NASM until parity tests exist; do not silently route it through an unverified fallback.
- Tests: cover
Some/None,Ok/Err, nested payload types, wrong payloads, duplicate/missing arms, and use of payloads outside a matching arm. - Docs and release gate: label the feature experimental until Linux GCC, Linux Clang, macOS Clang, sanitizers, and backend parity tests pass.
Non-goals for the first milestone
- General user-defined generics.
- Implicit conversions or exception-like hidden control flow.
- Assuming all errors are strings.
- Claiming full assembly-backend support before executable tests pass.
Acceptance criteria
- Stable 1.1.x examples remain unchanged.
- Every invalid payload construction is rejected with a targeted diagnostic.
- Exhaustiveness and payload scope are checked at compile time.
- The generated program has deterministic behavior for every variant.
- CI proves the same observable behavior on supported C compilers.
YLang 2.0 — Language and Ecosystem Design
Status: proposal, not an implementation guarantee.
This document defines the order in which YLang 2.0 should grow. The guiding rule is to land small, testable language changes rather than introducing syntax that the semantic analyzer and backends cannot enforce.
Goals
- Keep YLang readable and approachable while making invalid states harder to express.
- Make compiler diagnostics and behavior consistent across GCC/Clang C builds and the experimental NASM backend.
- Define type, initialization, ownership, and error-handling semantics before exposing pointers or object-oriented features.
- Ship reproducible compiler releases, a package format, documentation, and a minimal official website.
Non-goals for the first 2.0 milestone
- Claiming Rust-level memory safety before ownership and lifetime rules are implemented and tested.
- Supporting every feature in every backend at once. Unsupported features must fail with a diagnostic rather than silently miscompile.
- A public package registry before dependency integrity, version resolution, and lockfile behavior are specified.
- Class inheritance, reflection, async/await, or JIT compilation in the first milestone.
Backend strategy
The C backend remains the default for broad language coverage; it emits C and invokes GCC/Clang. The NASM backend is experimental, restricted to Linux x86-64, and must reject unsupported constructs. YLang does not yet have a complete general-purpose native backend independent of GCC/Clang. The plan is to define a shared typed intermediate representation, keep backend capabilities explicit, and require differential output/exit-status tests for the subset supported by multiple backends. A future proof of concept may evaluate LLVM IR or direct machine-code generation; neither is promised for the 2.0 beta. See BACKEND-ROADMAP.md.
Milestone 0 — Compiler correctness
Before new type syntax:
- Make definite-initialization analysis consistent for assignments, compound assignments, increment/decrement, branches, and loops.
- Add regression tests for each diagnostic and valid programs near those boundaries.
- Test the C backend with GCC and Clang; run AddressSanitizer and UndefinedBehaviorSanitizer in CI where supported.
- Keep the NASM backend explicitly experimental until its supported-language matrix and parity tests are clear.
- Run generated executables and compare output and exit status; a successful build alone does not prove runtime correctness.
Acceptance criteria:
- Each known bug has a regression test.
- CI reports the compiler, target OS/architecture, and sanitizer result.
- No feature is marked stable only because it parses.
Milestone 1 — Value types: struct and enum
Start with value types, not heap-allocated classes.
Current experimental subset (available on the development branch; not yet a stable 2.0 guarantee):
struct Point {
float x;
float y;
}
function distance_squared(Point point) -> float {
return point.x * point.x + point.y * point.y;
}
function main() -> int {
let Point point = Point(3.0, 4.0);
print(distance_squared(point));
return 0;
}
Unit enums and exhaustive match statements are also implemented experimentally. See STRUCTS.md and ENUMS.md for the tested subset and explicit limitations.
Required rules:
- A struct declaration has a stable, compile-time-known field layout.
- Every field must be initialized before an instance can be read.
- Field reads and writes are type-checked; unknown fields produce a dedicated diagnostic.
- Duplicate fields and duplicate type declarations are rejected.
- Copy/value semantics must be documented before structs can be passed, returned, or nested.
- Recursive by-value fields are rejected unless a later indirection type makes their size finite.
- Structs must not be accepted by the NASM backend until layout and field operations are implemented there.
Then add enums with exhaustive matching. Do not add pattern matching syntax until exhaustiveness and unreachable-case rules are specified.
Milestone 2 — Explicit absence and errors
Not implemented yet. Introduce Option<T> and Result<T, E> only after the compiler has a reliable generic/type representation. The current enum implementation has unit variants only and cannot carry payload values.
Illustrative future syntax (not currently guaranteed to compile):
let result: Result<int, ParseError> = string.parse_int(text);
match result {
Ok(value) => print(value),
Err(error) => print(error.message),
}
Rules:
- Missing values must not be represented by undocumented magic integers or null strings.
- Option<T> must force callers to handle the absent case, using matching or a documented unwrap API.
- Result<T, E> must distinguish success from failure and preserve the error value.
- Exhaustiveness checks must reject missing cases.
- Generic instantiation and diagnostics need tests for nested types and invalid type arguments.
Milestone 3 — Memory model before pointers
Do not add raw pointer syntax before deciding who owns allocations and how long references remain valid.
Proposed order:
- Define value versus owned heap types.
- Define deterministic cleanup semantics and behavior on early return.
- Add immutable and mutable references with explicit aliasing rules.
- Reject references that can outlive their referent.
- Add raw pointers only for low-level interoperability, behind an explicit unsafe boundary.
- Provide checked allocation and null handling at the safe API boundary.
Safety requirements:
- No read of uninitialized storage.
- No safe use-after-free or dangling reference.
- No safe dereference of null.
- No unchecked pointer arithmetic in safe code.
- Bounds checks remain in place for safe indexing.
- FFI and unsafe operations must be visibly marked and documented.
A runtime tracker that frees allocations at process exit is not a substitute for an ownership model. A borrow checker is a substantial feature and should be implemented incrementally with compile-fail tests.
Milestone 4 — Object-oriented features (deferred; not part of 2.0)
OOP is intentionally deferred. Do not add methods, classes, inheritance, or receiver semantics to the 2.0 release work; focus on Windows portability, YSPM system-package integration, and the playable minimal Game2D sample.
Add methods only after structs and field access are stable.
Preferred initial model:
- Methods associated with a named type.
- Explicit receiver semantics (self by value or by reference).
- Encapsulation through access modifiers with documented defaults.
- Interfaces/traits for behavior shared across types.
- Composition preferred over inheritance initially.
Class/reference semantics and inheritance are separate design decisions. Do not silently make all structs heap objects or add inheritance without defining identity, initialization order, dispatch, destruction, and cycle behavior.
Milestone 5 — Generics and standard library
Generic types and functions must specify type inference, constraints, monomorphization/code generation, and error messages. Start with a small set of constraints rather than unrestricted templates.
Standard-library priorities:
- Option/Result and string parsing.
- File and path APIs with explicit error handling.
- Collections with clear ownership and bounds behavior.
- Tests and documentation for every public API.
- Platform-specific APIs isolated behind portable interfaces.
Milestone 6 — YSPM Linux system-package integration (required for 2.0)
YSPM (https://github.com/Yassine-Jemi01/yspm) is a Linux system package manager that installs native system packages in the .yspkg format. It is not a package manager for project-local .yl module dependencies, so YLang must not pretend that YSPM resolves source imports or replaces YLANG_PATH.
YLang will provide a release packaging script at packaging/yspm/build-package.sh. It stages the compiler, source-level standard library, optional GUI runtime sources and public header, license, and available documentation, then asks the YSPM CLI to produce a native Linux x86-64 package. CI builds and inspects the package from the exact candidate commit. The beta.2 release workflow builds and attaches the generated .yspkg to the prerelease from the exact tested commit; already-published tags are never overwritten.
The 2.0 release gate requires:
- The package builds from the exact tested commit using a pinned YSPM source revision.
- CI verifies metadata, ABI/architecture, packaged compiler path, standard-library files, and that the packaged compiler starts.
- The release pipeline attaches the package artifact to the new prerelease only after all required gates pass.
- Documentation clearly says that YSPM manages Linux system packages; it is not a language-level module manager.
- The package documents external build prerequisites (GCC or Clang, plus SDL2 development dependencies and pkg-config for GUI/game builds).
YSPM remains a Linux-only optional distribution path. The ordinary source build remains the cross-platform development path.
Milestone 7 — Native Windows support (required for 2.0)
The target is native 64-bit Windows using MinGW-w64. MSVC is not part of the first portability milestone. The C backend stays the default; NASM remains Linux x86-64-only.
The Windows release gate must build the compiler and SDL2 GUI runtime, build and run a native executable, cover relative imports, and verify standard input. These smoke tests establish an initial supported path, not complete Windows feature parity. Continue expanding platform-specific tests after the first milestone.
See WINDOWS.md for setup instructions, dependencies, supported paths, and limitations.
Milestone 8 — Website, documentation, and 2D game readiness (required for 2.0)
The website and docs must describe tested behavior rather than the roadmap alone. They must cover:
- Installation on Linux, macOS, and experimental native Windows.
- YSPM .yspkg installation, Linux-only scope, package metadata, and external build prerequisites.
- The Game2D API, SDL2 prerequisites, install instructions, and a runnable game example.
- Current C/NASM backend limitations and the future compiler roadmap.
The minimal Game2D acceptance target is the ability to build and run at least one simple playable 2D game from YLang source. The current Dodge sample demonstrates frame timing, keyboard movement, rectangle drawing, collision detection, and a score. This is a small learning/prototyping library, not a full Pygame replacement.
The following remain future work and are not release blockers for the first 2.0 milestone:
- Object-oriented programming, methods on structs, and inheritance.
- A central package registry, dependency solver, and package signing.
- Sprite and texture management, audio, text rendering, animation systems, scene management, advanced physics, or 3D.
- A general-purpose native compiler backend independent of GCC/Clang.
Milestone 9 — LLVM and independent native compiler (future releases)
Do not attempt to finish LLVM as part of the current 2.0 release gate. Keep the existing C backend as the broad-coverage reference path and NASM as an experimental Linux x86-64 backend. In later releases, introduce a documented typed, backend-neutral IR and evaluate LLVM IR/object output against direct code generation. Expand a backend only when its output and exit status pass differential tests against the reference behavior.
CI and release gates
Every pull request:
- Builds with GCC and Clang on supported Linux runners and the native Windows MinGW-w64 smoke target.
- Runs unit/integration tests and verifies expected output and exit codes.
- Runs sanitizers where available.
- Runs compile-fail tests and checks stable diagnostic codes.
- Tests NASM only on supported Linux x86_64 runners with NASM and binutils installed.
- Builds and inspects the YSPM Linux package using a pinned YSPM source revision.
- Checks Game2D source compilation and links the playable example when SDL2 is available.
- Checks formatting, website links, and generated runtime artifacts.
Every release:
- Repeats the required test matrix on the exact candidate commit.
- Blocks publication until native Windows, the YSPM package artifact, and the playable Game2D example pass their acceptance checks.
- Publishes notes that distinguish implemented features from future work.
- Does not call a feature stable if its acceptance tests are missing.
Reference designs
These documents are references for principles, not specifications to copy wholesale:
- Swift memory safety and definite initialization: https://docs.swift.org/latest/documentation/the-swift-programming-language/memorysafety/
- Swift type initialization rules: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/
- Rust references and borrowing: https://doc.rust-lang.org/core/primitive.reference.html
- Python packaging manifest specification: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
Decision log
- Keep the current C backend as the stable baseline while experimental NASM support is incomplete.
- Implement struct as the first aggregate type; do not conflate structs with classes.
- Specify ownership and lifetime rules before safe pointers.
- Prefer explicit errors (Result) over hidden failure states, but keep Result syntax out of scope until implemented.
- Require native Windows smoke tests, the YSPM package artifact, and a playable minimal Game2D example before publishing a 2.0 beta.
- Defer OOP and the full LLVM/native backend to future releases.
- Treat the website, package manager, documentation, and compiler artifacts as release outputs that must reflect verified builds.
Native Windows support (experimental)
YLang’s native Windows work targets 64-bit Windows with the MinGW-w64 toolchain. The Windows CI smoke job builds the compiler, builds the optional SDL2 GUI C runtime, compiles and runs a simple native program, verifies relative multi-file imports, and tests standard input.
This is the first portability milestone, not a claim that every YLang feature has full Windows parity. NASM output remains Linux x86-64-only. MSVC is not a supported build toolchain for this milestone.
1. Install MSYS2 and the toolchain
Install MSYS2 from https://www.msys2.org/, then open the MSYS2 MINGW64 terminal (not the plain MSYS terminal).
Update the package database and install the tools:
pacman -Syu
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2 mingw-w64-x86_64-curl mingw-w64-x86_64-pkgconf make python
Ensure the MinGW64 bin directory is on PATH when using YLang from a Windows terminal:
C:\msys64\mingw64\bin
The exact directory can differ if MSYS2 was installed somewhere other than C:\msys64.
2. Build the compiler
Clone the repository from the MINGW64 terminal and build:
git clone https://github.com/Yassine-Jemi01/ylang.git
cd ylang
make clean all CC=gcc
./build/ylang.exe --version
Run the regression suite where the command dependencies are available:
make test CC=gcc
Windows CI currently runs a focused native smoke suite rather than claiming that every POSIX-only shell integration test has been ported.
3. Compile a native Windows program
Create hello.yl:
function main() -> int {
print("Hello, Windows");
print("Answer:", 40 + 2);
return 0;
}
Then:
./build/ylang.exe check hello.yl
./build/ylang.exe build hello.yl -o hello.exe
./hello.exe
The default C backend generates C and invokes GCC or Clang. Use a compiler executable that is available in the environment’s PATH.
4. Multi-file imports
Relative imports work from the importing source file:
from "./math.yl" import answer;
YLang treats Windows paths case-insensitively when deduplicating resolved modules. The module search environment variable uses Windows’ semicolon-separated path-list convention:
In the MSYS2 MINGW64 shell, use forward slashes in drive paths:
export YLANG_PATH='C:/dev/ylang-libs;D:/shared/ylang'
In PowerShell, use:
$env:YLANG_PATH = "C:/dev/ylang-libs;D:/shared/ylang"
5. Optional GUI and Game2D
The compiler can validate GUI calls without SDL2 or libcurl, but GUI builds require SDL2 development files and pkg-config. Programs using the experimental HTTP namespace additionally require the MinGW-w64 libcurl package and pkg-config; see HTTP and API Requests. The MINGW64 package command above installs both.
The initial GUI runtime supports one active window, event polling, keyboard/mouse state, timing, background clearing, and filled rectangles. Game2D is intended for simple 2D prototypes and examples. Sprites, audio, in-window text rendering, scene management, and a full physics engine are not part of this milestone.
Current limitations
- Windows support is experimental and the CI smoke tests cover only a focused path through the compiler/runtime.
- MSVC is not supported by this build recipe; use MinGW-w64 GCC.
- The experimental NASM backend is Linux x86-64-only and does not compile all language features.
- Some developer tests and installation scripts still need platform-specific follow-up.
- Installing YLang system-wide and packaging all optional runtime dependencies on Windows is still future work.
When reporting a Windows issue, include the Windows version, MINGW64 package/toolchain versions, exact command, source files, full output, and whether the problem affects check, generated C compilation, or execution.
YSPM integration for YLang on Linux
YSPM is a Linux system package manager. It installs native system packages in the .yspkg format. It is not a package manager for importing .yl modules into an individual YLang project.
YLang’s release pipeline can build a native .yspkg so Linux users of a compatible YSPM system can install the compiler and its source-level standard library through YSPM. This is an optional Linux distribution path; normal source builds remain available on Linux/macOS, and native Windows follows the MinGW-w64 guide.
Build a .yspkg locally
Requirements:
- Linux x86-64 with a C compiler and GNU Make.
- Go 1.23 or newer to build YSPM from source.
- A YSPM version that supports the native .yspkg build format and the
yspm buildcommand.
Build YSPM and then the YLang package from the repository root:
From a workspace with sibling ylang and yspm directories:
cd ..
git clone https://github.com/Yassine-Jemi01/yspm.git
cd yspm
go build -o yspm ./cmd/yspm
cd ../ylang
YSPM_BIN="../yspm/yspm" bash packaging/yspm/build-package.sh
The script builds YLang for /usr, stages the compiler, .yl standard-library files, GUI runtime sources, public GUI header, license, and available documentation, then asks YSPM to produce:
build/ylang-yspm/ylang-<version>-x86_64.yspkg
The package builder defaults to ABI yspm-abi-1 and architecture x86_64. Override these only to match the target YSPM repository/system. The script defaults to static linking for the compiler executable to reduce undeclared runtime shared-library requirements; set YLANG_PACKAGE_LDFLAGS only when intentionally building a different variant.
Install a published package
When a .yspkg asset is published for a YLang release, download that asset and install it with YSPM:
sudo yspm install ./ylang-<version>-x86_64.yspkg
The exact package filename depends on the release version. The package can also be built locally using the command above.
The system package installs the ylang executable in /usr/bin, source modules in /usr/share/ylang/stdlib, the optional GUI runtime source, the GUI public header, and documentation under /usr/share/doc/ylang.
For named imports of installed standard-library source modules, add the shared standard-library directory to YLANG_PATH when needed:
export YLANG_PATH=/usr/share/ylang/stdlib
Important boundaries
- YSPM manages Linux system packages, not project-local YLang module dependencies.
- The .yspkg is a Linux x86-64 package intended for a compatible YSPM ABI. It is not a macOS or Windows installer.
- The package contains the YLang compiler and source modules, but does not automatically install GCC/Clang, GNU Make, SDL2 development files, or pkg-config.
ylang buildcompiles generated C and therefore requires GCC or Clang on the host. GUI/game builds additionally require SDL2 development dependencies and pkg-config. - The YSPM repository catalog and release signing are managed in the YSPM repository. Publishing a .yspkg as a YLang release asset does not automatically add it to every YSPM repository index.
- A YSPM package is not considered published until the corresponding release asset has been built and attached successfully by CI.
YLang Game2D: the first playable-game library
The experimental game2d.yl module is a small, source-level helper layer over YLang’s optional SDL2 GUI runtime. It is intended for learning and small 2D prototypes, not as a full game engine.
What is included
- Create and close one window.
- Run a frame loop with event polling, background clear, and present.
- Read keyboard state, mouse position, and mouse buttons.
- Measure elapsed time between frames.
- Draw filled rectangles with RGB colors.
- Test rectangle overlap and point-in-rectangle.
- A complete playable Dodge example that demonstrates movement, timing, collision detection, and score.
This version does not yet provide sprites or image loading, text rendering, audio, animation systems, physics, scene management, or multiple active windows. image.open() opens a file in the operating system’s default viewer; it does not draw a sprite inside the game window.
Requirements
- A YLang checkout with the compiler built.
- SDL2 development files and pkg-config.
- A graphical desktop session to run games. CI can check and compile the code, but it cannot interactively test a real game window.
For Fedora:
sudo dnf install SDL2-devel pkgconf-pkg-config
For Debian or Ubuntu:
sudo apt install libsdl2-dev pkg-config
For macOS with Homebrew:
brew install sdl2 pkg-config
Build and play the example
From the repository root, build YLang first, then check and build the sample:
make
make test
make game2d-example
./build/dodge
Controls:
- Left Arrow: move the player left.
- Right Arrow: move the player right.
- Escape: quit.
- Avoid the falling red block. The score increases each time it passes you.
The example writes instructions and the final score to the terminal. The current renderer has no text-drawing API, so the score is not drawn inside the window.
Install the optional runtime and helper module
make install-gui PREFIX="$HOME/.local"
For a user-local installation, make the installed YLang module visible to imports by setting YLANG_PATH to the share directory. For example:
YLANG_PATH="$HOME/.local/share/ylang" ylang build main.yl -o game
Then import the installed module by its path:
from "stdlib/game2d.yl" import game2d_open, game2d_begin_frame, game2d_draw_rect, game2d_end_frame, game2d_close, game2d_key_down, game2d_delta_ms;
The example in this repository uses a relative import because it is intended to run from the checkout.
API reference
| Function | Purpose |
|---|---|
| game2d_open(title, width, height) | Initialize SDL2, create a window, and start the frame timer. Returns bool. |
| game2d_begin_frame(red, green, blue) | Poll events and clear the frame background. Returns false when the user closes the window or an error occurs. |
| game2d_draw_rect(x, y, width, height, red, green, blue) | Draw a filled rectangle. Coordinates start at the top-left. |
| game2d_end_frame() | Present the finished frame. |
| game2d_key_down(name) | Test whether a key is held, e.g. “Left”, “Right”, “Space”, or “Escape”. |
| game2d_mouse_x() / game2d_mouse_y() | Read the current mouse position. |
| game2d_mouse_button_down(button) | Test a mouse button; left is 1, middle 2, right 3. |
| game2d_ticks_ms() | Read elapsed SDL time in milliseconds. |
| game2d_delta_ms() | Get milliseconds since the previous call. Clamp large values after pauses to avoid big simulation jumps. |
| game2d_rects_overlap(ax, ay, aw, ah, bx, by, bw, bh) | Axis-aligned rectangle collision test. Rectangles that only touch at an edge do not overlap. |
| game2d_point_in_rect(px, py, rx, ry, width, height) | Check whether a point is inside a rectangle; the right and bottom edges are excluded. |
| game2d_last_error() | Return the last GUI/runtime error message. |
| game2d_close() | Close the window and shut down SDL2. Call it before returning from the game. |
game2d_delta_ms() initializes its timing baseline when game2d_open() succeeds; clamp unusually large deltas after pauses. The collision helpers are deterministic and do not open a window, but game2d.yl also wraps the optional GUI runtime, so importing the combined module still requires SDL2 even when a program only calls the collision helpers.
RGB channels must be between 0 and 255, and rectangle widths and heights must be positive. Always exit the loop cleanly and call game2d_close() so SDL resources are released.
Example API pattern
from "stdlib/game2d.yl" import game2d_open, game2d_begin_frame, game2d_draw_rect, game2d_end_frame, game2d_close;
function main() -> int {
if (not game2d_open("My game", 640, 400)) {
return 1;
}
loop() {
if (not game2d_begin_frame(20, 24, 35)) {
break;
}
if (not game2d_draw_rect(80, 80, 40, 40, 80, 190, 220)) {
break;
}
if (not game2d_end_frame()) {
break;
}
}
game2d_close();
return 0;
}
The API and helper implementation are experimental and may change before the stable YLang 2.0 release.
YLang 2.0.0 — Stable Release
YLang 2.0.0 establishes the stable compatibility contract for the documented core language and the default C-backend compiler workflow. This release keeps the language’s explicit typing, source-located diagnostics, and inspectable C-generation pipeline as the supported foundation.
Important compatibility boundary: not every component introduced during the 2.0 development cycle is stable. The experimental subsystems listed below may change in future releases and are explicitly outside the stable-core compatibility guarantee.
Stable core
- Explicit declarations, constants, fixed-size homogeneous arrays, scalar types, functions, and structured control flow.
- Explicit conversions and type checking, initialization checks, clear diagnostics, and safe rejection of unsupported syntax.
- The default
ylang buildpath: validate.ylsource, generate C, and invoke GCC or Clang for a native executable. - Core strings, formatted strings, standard input, text file I/O, math helpers, path utilities, and checked runtime behavior documented by the core specification.
- The
check,build,emit-c, andfixcommand-line workflows, with the limitations of each command documented in the README.
The stable language contract is defined in the core language specification. Where this release notes page and the reference differ, the specification and the explicit experimental labels in the relevant feature guide define the supported behavior.
Experimental extensions
These features are available for evaluation, but are not covered by the stable-core compatibility promise:
- Named imports and multi-file compilation. No package registry, dependency solver, import aliases, or qualified module namespaces are promised.
- Structs and enums. Several advanced forms remain unsupported; consult the struct reference and the enum reference.
- GUI and Game2D. The optional SDL2-backed APIs are not a mature general-purpose game engine. Interactive window behavior requires a graphical session.
- NASM backend. Restricted to a documented Linux x86-64 subset; the C backend remains the default.
- Native Windows support. Experimental 64-bit MinGW-w64 path with focused smoke tests; MSVC and full feature parity are not claimed.
- YSPM integration. Optional Linux x86-64
.yspkgdelivery for compatible YSPM systems. It is not a Windows/macOS installer and does not supply GCC/Clang or SDL2 development dependencies.
Not implemented in 2.0.0
Option<T>andResult<T, E>are not implemented; their design document is a proposal only.- Classes/OOP, methods on structs, inheritance, generics, and a complete LLVM or independent native backend are not part of this release.
- Arrays cannot be returned or nested, and
charis one byte rather than a Unicode scalar value. - YLang does not claim Rust-level memory safety, sandboxing, or suitability for processing hostile source as a hardened security boundary.
Build from source
Requirements are GCC or Clang with C17 support and GNU Make (gmake on macOS). For ordinary command-line programs, SDL2 is optional. GUI/Game2D builds additionally need SDL2 development files and pkg-config.
git clone https://github.com/Yassine-Jemi01/ylang.git
cd ylang
make
make test
./build/ylang --version
On macOS, use gmake for these build and test commands. Native Windows users should follow the Windows guide and treat that path as experimental.
Linux YSPM package
The stable release workflow builds the Linux x86-64 .yspkg from the exact tested release commit, inspects its contents, runs the packaged compiler, and attaches it only after every stable-release gate passes. Install it only on a compatible YSPM system. Consult the YSPM integration guide for ABI, filesystem, and dependency details.
Upgrade and bug reports
Projects moving from YLang 1.1.0 should first run their test suite with 2.0.0 and review experimental feature notes if using imports, structs, enums, GUI/Game2D, NASM, Windows, or YSPM integration.
When reporting a bug, include the exact version and commit, operating system, compiler, command line, a minimal reproducer, expected result, and actual result.
YLang Memory Model — RFC
Status: partial implementation in the experimental C backend. The compiler has an explicit owned_string type with move-only binding transfers and generated cleanup at ordinary block exits, function returns, loop breaks, and loop continues. A restricted immutable borrowed_string function-parameter view is also implemented. General &T / &mut T borrow syntax, conflicting-borrow checks, and full Rust-like lifetime analysis are still unimplemented.
Goals
- Keep the default language free of a tracing garbage collector and of periodic GC pauses.
- Free owned heap allocations deterministically when their owner leaves its lifetime.
- Reject use-after-move, dangling borrows, and conflicting mutable borrows at compile time.
- Preserve checked integer arithmetic and array bounds checks unless the compiler proves a check unnecessary.
- Avoid reference counting on ordinary owned values; no cycle collector should be required for the core model.
- Keep the stable 2.0.x language behavior unchanged while the feature is developed behind explicit tests and a release gate.
Non-goals for the first milestone
- A tracing garbage collector.
- Shared mutable ownership, weak references, or cyclic owning references.
- A global allocator replacement or a promise that every program will be faster than C.
- Silently changing existing
string, array, or struct behavior before a migration plan exists. - Treating C code generation as a memory-safety sandbox.
Proposed ownership categories
The compiler must classify values rather than relying on the C type alone:
- Scalar values:
int,float,bool,char, and unit-enum values. These are copied by value. - Owned values: initially dynamic strings, and later owning structs/containers. Every owned value has exactly one owner at a time. Assignment or pass-by-value moves ownership; the old binding becomes unavailable.
- Shared immutable borrows:
&T. Multiple immutable borrows may coexist while the owner remains alive. - Exclusive mutable borrows:
&mut T. Only one exclusive mutable borrow may be active, and no immutable borrow may overlap it. - Non-owning views: any future slice/string-view type must carry a checked lifetime and may never outlive its backing owner.
The precise surface syntax and which standard-library operations borrow or consume a value must be finalized before implementation. The notation above is a design direction, not currently compilable syntax.
Lifetime and cleanup rules
- A local owner is destroyed at the end of its lexical scope.
- Returning an owned value transfers ownership to the caller.
- Returning a borrow is allowed only when the type system can prove that the referent outlives the returned borrow; the first implementation may forbid borrowed returns.
- Passing an owned value to an ordinary by-value parameter transfers ownership. A borrow parameter does not consume its argument.
- Every control-flow exit (
return,break,continue, and normal block exit) must run exactly the destructors for owners whose scopes are exited. - Cleanup must be emitted on all paths. The generated C must not rely on non-portable compiler extensions for destructors.
- A resource is released exactly once. Use-after-move and double-release must be rejected or made impossible by generated code.
- If ownership cannot be determined, compilation must fail with an actionable diagnostic rather than guessing.
Current experimental progress
Implemented: experimental owned_string and explicit moves
The C backend and semantic checker support:
let owned_string source = string.own("YLang");
let owned_string joined = owned_string.concat(source, "!");
let owned_string destination = move(joined);
print(source, destination);
owned_string is a separate experimental type. string.own(text) clones text into tracked heap storage; owned_string.concat/replace/upper/lower and io.read_line_owned / io.read_file_owned return owned values. The source of move(name) becomes unavailable (diagnostic E2110) until reinitialized. Implicit copies of owned bindings are rejected (E2115); returning an owned local requires return move(name) (E2116). Moves are merged conservatively across branches, loops, and exhaustive match arms. Self-move assignment is rejected (E2114).
Generated C keeps owned slots initialized to null and emits yl_release calls on ordinary scope exit and before return, break, or continue. Transferred values are nulled at the source, so cleanup is safe to run on every path. Assigning a new owned value releases the previous value. The runtime release helper only frees pointers registered by YLang’s allocation tracker, so string literals and other untracked pointers are not freed accidentally.
The existing string type remains a legacy C-string representation; runtime-created legacy strings can still be retained until process exit. owned_string is supported only by the C backend for now. Global owned strings, arrays of owned strings, and owned-string struct fields are rejected (E2118) until global, array-element, and field cleanup are implemented. User functions that accept owned_string take ownership of that argument; call them with move(name) or a fresh owned expression.
The separate experimental borrowed_string type is permitted only for function parameters. Such parameters are immutable, are passed as non-owning pointers, and cannot be stored in a local/global variable, a struct field, or a return type. They may be forwarded only to other borrowed_string parameters or used immediately by non-retaining string helpers. An owned_string temporary is rejected when passed into a borrowed parameter because the temporary would have no named owner to keep it alive for the call. This provides a narrow immutable parameter view, not a general lifetime checker: the language still has no &mut T, no returned borrows, and no general alias analysis.
The first runtime instrumentation commit adds an opt-in diagnostic:
YLANG_MEMORY_STATS=1 ./your-program
When the generated program exits normally, it writes a line to standard error with the number of currently tracked payload allocations and their requested payload bytes. The counters saturate rather than wrapping. This is diagnostic instrumentation only: it does not reclaim memory earlier, the byte count excludes tracker-node and allocator overhead, and it is not a measurement of resident RAM. Programs using the existing tracker still retain those allocations until yl_cleanup().
The test suite includes an opt-in fixture and checks that enabling the diagnostic does not alter normal program output. Ownership, borrowing, and scope-based release are not implemented by this instrumentation.
First implementation slice
Implement this in small, reviewable steps:
- Flow-sensitive ownership facts (in progress): current checks model explicit moves and merge them conservatively, but complex control-flow should continue to gain regression cases.
- Borrow checker: design explicit immutable and mutable borrow syntax, then reject dangling/conflicting borrows, borrowed moves, and returned local references.
- Signature and inter-module ownership contracts: make ownership transfer vs borrow behavior explicit for all functions and imported signatures.
- Cleanup coverage: expand and verify cleanup for all control-flow edges, then add arrays, structs, globals, and nested owners only when their drop semantics are defined.
- Runtime verification: test allocations/frees under AddressSanitizer and LeakSanitizer where available, plus repeated-request stress tests.
- Expand only after parity: add arrays and structs with owned fields once strings pass the full test matrix.
Required compile-pass tests
- Moving an owned value makes its source binding unavailable.
- An immutable borrow can be read multiple times while its owner is alive.
- Mutation through
&mut Tis permitted only with exclusive access. - Returning an owned value keeps it valid in the caller.
- Inner-scope cleanup does not invalidate values that were moved out.
- All valid examples have identical observable behavior under GCC and Clang.
Required compile-fail tests
- Use after move.
- Borrow after owner destruction / returning a local borrow.
- Two overlapping mutable borrows.
- Mutable and immutable borrows that overlap incompatibly.
- Double move from one binding.
- Moving a value while it is borrowed.
- Invalid use of a moved value on any control-flow branch.
Performance and memory acceptance criteria
- No regression in stable 2.0.x behavior before the feature is explicitly enabled.
- For a long-running test that repeatedly creates and discards owned buffers, live owned bytes must return to a bounded baseline after each request/scope completes.
- Measure peak RSS, total allocated bytes, total freed bytes, and request throughput; RSS alone is insufficient because allocators may retain freed pages.
- Compare equivalent workloads with equivalent overflow/bounds semantics and the same native compiler flags.
- No performance claim based on one microbenchmark. Use repeated runs, warmups, multiple workload families, and report medians and variance.
- Keep checked arithmetic and bounds checks unless proven redundant by analysis. Any check-elision optimization needs tests that cover both provably safe and potentially overflowing inputs.
Open design decisions
- Whether current
stringremains a copyable borrowed view and a newStringtype owns bytes, or whether a future major release changesstringto an owned, move-only value. - Borrow syntax and inference rules.
- Whether array slices are introduced with the first owned type or later.
- How imported functions expose ownership/borrow information in module signatures.
- How debug diagnostics explain ownership conflicts without producing cascaded errors.
YLang 2.1.0 — Release Candidate Notes
Status: candidate preparation only. This is not a published release.
YLang 2.1.0 is intended to introduce an experimental ownership milestone while preserving the existing legacy string behavior and the documented 2.0 stable-core contract.
Experimental ownership additions
owned_stringrepresents a tracked, owned string allocation in the experimental C backend.string.own(text)creates an owned copy.move(name)explicitly transfers ownership and makes the source binding unavailable until it is reinitialized.- Owned-string helpers include
concat,replace,upper, andlower; owned input helpers includeio.read_line_ownedandio.read_file_owned. - Generated cleanup is emitted for owned locals at normal scope exit and before
return,break, andcontinue. borrowed_stringis available as a restricted immutable function-parameter view.
Important limitations
This milestone is not a complete Rust-like ownership or borrowing system.
- These ownership features currently require the C backend.
- Owned globals, arrays of owned strings, and owned-string struct fields remain unsupported.
borrowed_stringis restricted to immutable parameters; borrowed returns and local/global/field storage are not supported.- General
&Tand&mut Tsyntax, full lifetime analysis, and general alias/conflicting-borrow analysis are not implemented. - The existing
stringtype retains its legacy allocation behavior and is not automatically made memory-safe by this change. - YLang does not claim Rust-level memory safety or provide a security sandbox.
Validation required before publication
Do not create the v2.1.0 tag or publish a GitHub Release until the candidate commit has passed the repository’s required release checks, including clean GCC/Clang builds, regression tests, sanitizers, native Windows smoke tests, editor diagnostics, website/book integrity, and YSPM package validation. Review the actual workflow results for the exact commit; a successful earlier PR run is not a substitute for testing this release candidate.
Upgrade guidance
Existing programs using string do not need to migrate to owned_string. Treat the new ownership APIs as experimental and opt in only where the documented restrictions are satisfied. Do not assume that adding owned_string makes all allocations in a program automatically reclaimed.
YLang 2.2.0 — Stable Release
YLang 2.2.0 expands the developer toolchain with experimental HTTP/API requests, asynchronous HTTP tasks, environment-variable access, and source-aware native debugging. The documented core language and default C-backend workflow remain the compatibility foundation.
Compatibility boundary: the HTTP client, HTTP-task async API, debugger integration, named imports, structs/enums, GUI/Game2D, NASM backend, native Windows path, and YSPM packaging have explicit experimental limitations. This release does not claim Rust-level memory safety, a security sandbox, general-purpose async functions, a built-in JSON parser/serializer, a complete VS Code Debug Adapter Protocol implementation, or Windows interactive debugging.
Highlights
- HTTP and API requests: synchronous helpers and
http.request(method, url, body, headers)for GET, POST, PUT, PATCH, DELETE, and HEAD through optional libcurl support. - Asynchronous HTTP tasks:
http.request_async(...)returns anhttp_task;await taskconsumes it and returns the response body;http.cancel(task)requests cancellation. The libcurl multi interface lets multiple transfers progress while tasks are awaited. - Response and error inspection:
http.status(),http.response_headers(), andhttp.last_error()expose the latest response status, headers, and transport/configuration error. - Environment access:
env.get(name)reads process environment variables without invoking a shell, allowing programs to obtain credentials without hard-coding them. - Security defaults: request-method and header validation, a 16 KiB header limit, an 8 MiB response-body limit, connect and overall timeouts, HTTPS certificate/hostname verification, and restricted redirect behavior for requests carrying custom headers.
- Source-aware debugger:
ylang debugbuilds with-O0and debug symbols, then launches GDB on Linux or LLDB on macOS. Generated C source mappings let supported breakpoints refer to original.yllocations. - Developer tooling: updated VS Code and Neovim syntax support for
httpandenv, a terminal-based VS Code debug command, and repeatable compile/runtime/binary-size benchmark tooling. - Documentation and website: refreshed homepage, online book, reference pages, and candidate-acceptance tooling.
Known limitations
- Async/await applies only to HTTP task handles. General async functions, a background scheduler, and GUI/event-loop integration are not implemented.
- HTTP response bodies are strings; a built-in JSON parser/serializer is not included.
- Programs that use
http.*require libcurl development files andpkg-configwhen linking. Ordinary programs do not require libcurl. - HTTP method names are case-sensitive and must be uppercase. HTTP status codes such as 404 are not transport failures; callers must inspect
http.status(). - Debugging is an initial CLI workflow, not a complete Debug Adapter Protocol implementation. Local-variable names and some scopes may reflect generated C. Interactive debugging is supported on Linux with GDB and macOS with LLDB; Windows and the NASM backend do not have a source-level debugger contract.
- Experimental extensions are not covered by the stable-core compatibility guarantee and do not provide a security sandbox or complete Rust-like memory safety.
Build and verify
Build from source with GCC or Clang and GNU Make:
make
make test
For HTTP-enabled programs, install libcurl development files and pkg-config. See HTTP and API Requests, the debugger guide, the security model, and the performance guide for requirements and details.
The candidate acceptance procedure is documented in ACCEPTANCE-2.2.0.md. YLang 2.2.0 does not include Marketplace publication of the VS Code extension; the extension can be packaged locally as a .vsix.
YLang 2.2.0 Candidate Acceptance
Do not publish 2.2.0 until this test has passed on the final candidate commit and the project owner has reviewed the behavior.
The repository’s GitHub Actions matrix tests GCC, Clang, macOS, Windows native smoke behavior, sanitizers, HTTP/async requests, source mapping and debugger launch, editor diagnostics, package contents, and the refreshed website. This local script consolidates the same acceptance work into one command:
bash tools/test-2.2-candidate.sh
It checks the normal compiler/regression suite, the HTTP/async integration fixture, the source-aware debugger path, a repeatable benchmark with known output, ASan/UBSan, VS Code tests and a packaged .vsix, Neovim tests when installed, the mdBook output and link integrity, and the React homepage build. The extension package is written under build/acceptance/. It requires GCC or Clang, Make, Python 3, pkg-config plus libcurl development files, GDB or LLDB, Node.js 22+, npm, and mdBook 0.5.4.
On Fedora, install the common build dependencies with:
sudo dnf install gcc clang make python3 pkgconf-pkg-config libcurl-devel gdb nodejs npm
Install mdBook 0.5.4 from its official installation instructions if it is not available in the system package sources: https://rust-lang.github.io/mdBook/guide/installation.html
Run the candidate without disturbing your current checkout
From your existing YLang clone, use a separate Git worktree so your current branch and uncommitted files stay untouched:
git fetch origin feature/2.2-implementation-mvp
git worktree add ../ylang-2.2-acceptance origin/feature/2.2-implementation-mvp
cd ../ylang-2.2-acceptance
bash tools/test-2.2-candidate.sh
For Clang, run the same script with NATIVE_CC=clang. After it passes, install the extension package in VSCodium with codium --install-extension build/acceptance/ylang-language-support-0.6.0.vsix (or use code --install-extension ... in VS Code), then reload the window and open the sample file. If the script fails, keep the full output and report the first failing step rather than publishing the candidate.
Passing CI is necessary but not enough. The project owner should also try the actual public-facing workflow once: run a small .yl program that starts two HTTP tasks, awaits them, checks the response/status, then launch ylang debug and set a source-line breakpoint. This final hands-on check must use the exact commit proposed for release.
The script builds and tests only; it does not push, merge, tag, deploy, or publish any release.
YLang 2.0.0 Stable Release Acceptance Checklist
This checklist applies only to the stable v2.0.0 candidate, not the previously published v2.0.0-beta.2. The default C backend and documented core language are the stable contract. Experimental extensions remain explicitly excluded as described in the stable release notes.
The stable publication workflow runs from main, tests that exact commit, and refuses to create a stable release while any acceptance gate below remains unchecked. Do not check a gate until its corresponding result has been observed and recorded.
Release scope and safeguards
-
VERSIONand.github/PUBLISH_STABLEidentify exactly2.0.0, with no prerelease suffix. - The previous Beta release and
.github/PUBLISH_BETAmarker are left untouched. - Stable publishing uses a separate workflow, triggered only by manual dispatch or the explicit
.github/PUBLISH_STABLEmarker change onmain; unrelated pushes and pull requests do not publish. - Stable publishing is permitted only from
main, confirms the version, refuses existing tags/releases, and tags the exact tested commit. - Stable notes, the README, language specification, website, and online-book navigation distinguish stable core behavior from experimental extensions.
Required automated checks for the stable candidate
- Clean GCC build and full
make teston Linux. - Clean Clang build, full regression tests, sanitizer suite, and bounded lexer/parser fuzz smoke test on Linux.
- Clean Clang build, GUI/runtime build checks, and regression tests on macOS.
- Native Windows MinGW-w64 smoke tests pass; this remains experimental support, not full platform parity.
- YSPM package is built from this exact commit; metadata and required paths are verified; the exact package is installed, used to build/run a program, and removed under an isolated
YSPM_ROOT. - VS Code and Neovim diagnostic tests pass.
- mdBook builds, all required reference chapters exist, and website integrity checks pass.
- The stable workflow checks out the event SHA, runs the required jobs against it, and creates the release tag at that same tested SHA only when every job succeeds.
Required manual acceptance checks for the stable candidate
- The Dodge example was run interactively against the 2.0 compiler/Game2D source baseline; this stable-candidate change does not modify compiler or Game2D source files.
- The exact stable package’s install/use/remove path is covered by
packaging/yspm/smoke-install.sh, which the required CI job runs inside a disposableYSPM_ROOT. - A clean clone, clean build, full regression test suite, CLI/version checks, and a compiled demo were exercised on the unchanged 2.0 compiler source baseline; the candidate version is rebuilt and tested by CI.
- The book’s stable/experimental labels and PDF-edition note were reviewed; CI rebuilds mdBook, checks the new release chapters, and validates the website’s links and anchors.
- Stable release notes were reviewed to keep the core guarantee separate from imports, structs/enums, GUI/Game2D, NASM, Windows, and YSPM experimental status.
Publication decision
Only publish v2.0.0 once every item above is checked and all CI jobs for the exact candidate commit succeed. The release workflow must create a stable GitHub Release (not a prerelease), attach the validated Linux x86-64 YSPM package, and never overwrite an existing tag or release. If any gate fails, fix the issue and test a new candidate commit before publication.
YLang 2.0.0-beta.2 — Release Notes
YLang 2.0.0-beta.2 is an experimental prerelease, not a stable release. The previously published v2.0.0-beta.1 remains source-only and unchanged. This beta adds the Linux x86-64 YSPM package artifact through a release workflow that rebuilds and checks the package on the exact candidate commit. Stable 1.1.0 remains the recommended line for projects relying on the documented stable subset.
Highlights
- Named imports and multi-file compilation: import named functions and global variables from relative source files, with diagnostics for missing modules or symbols and duplicate imports.
- Typed enums and exhaustive matching (experimental): unit enum variants, enum values in variables and function signatures, type checks, and exhaustive
matchstatements. - Typed structs (experimental): positional construction, field access, passing and returning struct values, and type-checked mutable field assignment.
- Optional SDL2 GUI runtime: window creation, event polling, drawing primitives, and cleanup APIs.
- 2D game helpers (experimental): drawing, input, timing, and collision helpers around the optional GUI runtime; the playable Dodge sample demonstrates a small game.
- YSPM Linux package integration (experimental): this prerelease includes a Linux x86-64
.yspkgbuilt from the release candidate for compatible YSPM systems. YSPM manages system packages; it does not resolve project-local.ylmodules. - Native Windows foundation (experimental): MinGW-w64 x86-64 CI smoke tests cover compiler build, native executable generation, relative imports, standard input, and SDL2 runtime build.
- Improved compiler validation: regression coverage, frontend unit tests, a lexer/parser fuzz smoke test, sanitizers, and editor-diagnostic tests in CI.
Backend status
- The C backend remains the default for
ylang build. - The NASM backend is experimental and supports only a documented subset. Do not assume that every C-backend program can be compiled to NASM.
- GUI/game programs require SDL2 development dependencies and a graphical display to run.
- Native Windows x86-64 support through MinGW-w64 is experimental. A focused Windows CI smoke job covers build/run, imports, input, and SDL2 linking; this is not full feature parity. See
docs/WINDOWS.md.
Known limitations
Option<T>andResult<T, E>are not implemented. The design document is a proposal, not working language syntax.- Enums currently have no payload variants, explicit discriminants, or enum formatting. Enum imports across module boundaries are not promised.
- Structs do not provide methods, inheritance, generics, field defaults, or named constructor arguments; recursive by-value fields are unsupported.
- The import system does not yet provide import aliases, qualified module namespaces, or circular imports. There is no official project-level
.ylpackage registry or dependency solver in this release; YSPM is not intended to provide that functionality. - The 2D/GUI APIs are experimental and should not be treated as a mature game engine.
- Object-oriented programming and a complete LLVM/independent native backend are intentionally deferred to later releases.
- This beta does not claim Rust-level memory safety or production stability.
Installation and artifacts
The release workflow builds the Linux x86-64 YSPM .yspkg on the exact candidate commit, validates package metadata and required files, starts the packaged compiler, and attaches the artifact to the prerelease. Install it on a compatible YSPM system using:
sudo yspm install ./ylang-2.0.0-beta.2-x86_64.yspkg
The package is an optional Linux system-package route; it is not a Windows or macOS installer. For source builds on Linux/macOS and the experimental MinGW-w64 Windows path, see README.md and docs/WINDOWS.md.
For GUI/game examples, install SDL2 development files and pkg-config. The CI validates compilation and linking, but live window interaction needs a graphical session and is not tested by headless CI. See docs/GAME2D.md and docs/YSPM.md.
Upgrade and bug reports
- Keep 1.1.0 for projects that need the stable documented subset.
- Test beta code in a separate branch or checkout before migrating projects.
- When reporting a bug, include the exact version/commit, operating system, compiler, command, a minimal reproducer, expected result, and actual result.
The 2.0.0-beta.2 version is for evaluation and feedback. Behavior and feature coverage may change before a stable 2.0.0 release.
YLang 2.0 Beta Release Gate
This checklist covers the implementation and automated acceptance gates for the next YLang 2.0 beta prerelease. The already-published v2.0.0-beta.1 remains source-only and will not be overwritten. The 2.0.0-beta.2 candidate keeps experimental features clearly labeled; stable 1.1.0 remains recommended for projects that need the documented stable subset.
The release workflow reruns the required checks on the exact candidate commit before creating a prerelease. A green CI run on the integrated main commit has already covered the compiler matrix, native Windows smoke tests, YSPM package construction/inspection, editor diagnostics, and website integrity. Interactive GUI behavior still needs testing on a graphical desktop and is not claimed by headless CI.
Version and publication safeguards
-
VERSIONand.github/PUBLISH_BETAmatch the candidate prerelease version. - The release workflow only publishes from
main; dispatching it from another branch cannot publish. - The release workflow checks out and tests the event commit, then creates the matching
v${PUBLISH_BETA}tag at that same SHA. - Publication is blocked if any checklist item is unchecked.
- Publication refuses to overwrite an existing release and refuses to publish against an existing tag without a verified release.
- Publication creates a GitHub prerelease, never a stable release.
Automated source and build checks
The release workflow reruns these checks on the exact candidate commit:
- Clean compiler build with GCC on Linux.
- Clean compiler build with Clang on Linux.
- Clean compiler build with Clang on macOS.
- Optional SDL2 GUI runtime and example compile/link checks on supported CI systems.
- Full regression suite, including frontend unit tests.
- Generated runtime-template consistency check.
- YSPM integration CI builds a
.yspkgfrom the candidate commit, validates package metadata and required files, and starts the packaged compiler. - Bounded lexer/parser fuzz smoke test on Linux with Clang.
- AddressSanitizer and UndefinedBehaviorSanitizer test suite.
- VS Code and Neovim diagnostic parser tests.
- Website integrity checks and deployment workflow.
Feature coverage and limits
- Imports cover relative paths, repeated module loading, imported functions/globals, missing files/symbols, duplicate imports, cycles, and unsupported entry-point imports.
- Struct tests cover construction, field access, passing/returning values, assignment, compound assignment, increment, and invalid fields/types/arity.
- Enum tests cover typed values, function parameters, comparisons, exhaustive matching, invalid variants, duplicate arms, and non-exhaustive matches.
- NASM differential tests compare the documented shared subset against the C backend on Linux x86-64; unsupported GUI and struct use must be rejected clearly.
- GUI/2D tests cover type checking, generated API calls, runtime/example builds, and non-window collision helpers.
Explicit beta exclusions
-
Option<T>andResult<T, E>are not implemented; the design document is a proposal, not working syntax. - The C backend remains the default. NASM is experimental, limited to a documented subset, and is not a complete alternative backend.
- Live GUI window interaction is not tested by headless CI; running GUI examples requires a graphical session.
- Native Windows x86-64 uses MinGW-w64 in an experimental smoke-tested path; full feature parity is not claimed.
- No general-purpose precompiled binaries or Windows/macOS installers are promised. The optional
.yspkgis for compatible Linux x86-64 YSPM systems only. - Structs, enums, module imports, and game/GUI APIs remain experimental and may change before stable 2.0.0.
- YLang does not claim Rust-level memory safety or production stability.
- Object-oriented programming, methods on structs, inheritance, and the complete LLVM/native backend are deferred to later releases.
Required implementation and acceptance gates for beta.2
- Native Windows x86-64 CI builds the compiler and SDL2 runtime, builds/runs a native executable, and tests imports and standard input.
- The release workflow builds a Linux x86-64 YSPM
.yspkgfrom the exact tested commit, verifies its contents and starts the packaged compiler, then attaches it to the new prerelease. - The Game2D library builds and links the playable Dodge example; API and setup instructions are documented. Run the example manually in a graphical session when available because that interaction is not covered by headless CI.
- The website and installation documentation describe the current feature set, tested platform support, experimental boundaries, YSPM package, and Game2D workflow.
Publication decision
The automated workflow is the final gate: it reruns the source CI matrix, Windows native smoke tests, YSPM package build/inspection, fuzz smoke test, sanitizers, editor checks, and website checks on the exact main commit. It publishes only if all jobs pass and this checklist contains no unchecked items. Existing tags and releases are never overwritten. These gates verify the beta scope and do not imply stable 2.0.0 status.