Tinox
A compiled, object-oriented programming language with Java-like syntax, LLVM backend, integrated HTTP server, and complete IDE tooling – designed for high-performance backend services.
What is Tinox?
Tinox is an independently developed, statically typed programming language that compiles directly to native machine instructions. The language combines a Java-like, annotation-driven syntax with the performance advantages of an LLVM-based compiler and a runtime system written in C.
The main focus is on the simple development of high-performance
HTTP REST services: a controller handler requires only a few lines of code,
and the built-in HTTP server is based on epoll and
SO_REUSEPORT – technologies also used in production-ready
web servers such as Nginx.
import tinox.core.http_server;
import tinox.core.json;
class SortController
{
@POST
@Path("/sort")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
fn sort(ctx: HttpContext) -> Nothing
{
let root: JsonValue = Json.parse(ctx.request.body);
let numbers: List<Int64> = Json.intArrayFromJson(root.getField("numbers"));
ctx.response.body = Json.intArrayWrap("sorted", numbers.sort());
}
}
This code represents a fully functional REST endpoint – no framework configuration overhead, no XML files, no boilerplate.
Technical Fundamentals
LLVM as Compiler Backend
The Tinox compiler does not generate native machine instructions directly, but rather LLVM Intermediate Representation (LLVM IR) – a platform-independent, typed assembly language. The LLVM toolchain then handles the optimization and translation to machine code.
LLVM is also used by Clang (C/C++), Rust, Swift, Julia, and Kotlin Native – it is one of the most mature compiler backends in the industry.
Rust as Implementation Language
The entire compiler – lexer, parser, type checker, code generator, and LSP server – is implemented in Rust. This brings the following advantages:
- Safety No undefined behavior, no memory errors in the compiler itself
- Performance Fast compile times of the compiler (no GC pauses)
- Concurrency Thread-safe data structures (DashMap) for the LSP server
C as Runtime System
The runtime system (runtime.c) is written in C and provides all
operating system-level functions: memory management, strings, arrays,
maps, file I/O, networking, and the HTTP server. C functions are called directly from
LLVM IR – no ABI overhead through wrapper layers.
Key Concepts
Static Typing
All types are checked at compile time. No runtime type errors possible.
AOT Compilation
Ahead-of-Time: the source code is fully compiled to a native binary before execution. No interpreter, no JIT.
Boehm GC
Tinox uses the Boehm Garbage Collector – a conservative, thread-safe GC. Memory is freed automatically; the developer does not need to manage anything manually.
1:1 Threading
Every spawn call creates a real OS thread (pthread).
Full parallelism on multiple CPU cores.
Components Overview
Tinox consists of several Rust crates (libraries) that together form the compiler and tooling, plus the C runtime system and the standard library written in Tinox itself.
| Component | Language | Purpose | Size |
|---|---|---|---|
tinox-common | Rust | Shared data types (Span, Error, AST nodes) | ~250 lines |
tinox-lexer | Rust | Tokenization (Lexer) | ~800 lines |
tinox-parser | Rust | AST construction + code formatter | ~2,500 lines |
tinox-typecheck | Rust | Type checking + annotation processing | ~2,000 lines |
tinox-codegen | Rust | AST → LLVM IR | ~6,000 lines |
tinox (CLI) | Rust | Compiler control, REPL, project manager | ~1,700 lines |
tinox-lsp | Rust | Language Server Protocol Server | ~1,000 lines |
tinox-eclipse | Java | Eclipse IDE Plugin | ~400 lines |
runtime.c | C | Runtime system: I/O, HTTP, memory, strings | ~1,700 lines |
tinox-core | Tinox | Standard library (55+ modules) | ~200 KB |
Compiler Pipeline
The Tinox compiler transforms a .tnx source file in several
steps into an executable native binary:
Lexer (tinox-lexer)
Reads the UTF-8 source text and breaks it into tokens (smallest
meaningful units): keywords, identifiers, literals,
operators, brackets. Each token carries precise position information
(line, column, byte offset) for later error messages.
Supports string interpolation ("Hello ${name}!").
Parser (tinox-parser)
Converts the token sequence into an Abstract Syntax Tree (AST) – a tree structure representing the syntactic structure of the program. Recursive descent parser: every grammar rule corresponds to a function. Recognizes classes, methods, expressions, loops, match expressions, etc.
Import Resolution (tinox CLI)
import declarations are resolved recursively: the imported
.tnx files (from the standard library or the project directory)
are loaded, parsed, and embedded into the AST. The result is a
single, consolidated AST with all required declarations.
Type Checker (tinox-typecheck)
Two-pass approach: first all declarations (classes, methods, functions) are registered, then expressions and statements are checked for type correctness. Also validates annotations (@GET, @POST, @Produces etc.), extracts REST routes, and recognizes DI components.
Code Generator (tinox-codegen)
Traverses the AST directly and generates LLVM IR as text. Generates vtable structures for interface dispatch, monomorphizes generic functions/classes, allocates closure environments, generates HTTP route handlers. No intermediate step – directly AST → IR.
llc (LLVM Compiler)
The external LLVM tool llc translates LLVM IR into
assembler. In release mode with -O3, extensive
optimizations (inlining, loop optimizations, vectorization) are performed.
Clang (C Compiler / Linker)
clang compiles runtime.c into an object file
and links it with the generated assembler into a finished
native binary. This also links -lm (math library).
Runtime (C Runtime System)
The core of the Tinox runtime system is runtime/runtime.c
(~1,700 lines of C code). It provides all primitives that the generated
LLVM IR accesses directly.
Memory Management
Tinox uses the Boehm Garbage Collector (libgc) for automatic
memory management. The GC is conservative: it scans the stack and registers for
pointers and frees objects that are no longer reachable – without the developer
having to call free() explicitly. With GC_THREADS it is
fully thread-safe and works correctly with the epoll-based
HTTP server thread pool.
For frequent operations (sort, JSON parsing), thread-local static buffers that are allocated once and then reused – this completely eliminates malloc overhead in the request-critical path.
Collections
| Type | Implementation | Special Feature |
|---|---|---|
| Array | Dynamic C array | Length stored at offset -1; grows on push |
| Map | Open-addressing hash table | FNV-1a hash, load factor 0.75, tombstones for delete |
| String | C string (char*) | Immutable; operations create new object |
Async & Concurrency
The concurrency model is based on POSIX threads (pthreads).
spawn creates a real OS thread. Channels internally use
a linked-list queue with mutex and condition variable.
HTTP Server (Low Level)
The HTTP server in the runtime uses:
- SO_REUSEPORT Multiple threads bind the same port – the kernel distributes connections
- epoll Event multiplexing: one thread manages up to 4,096 simultaneous connections
- TCP_NODELAY Nagle algorithm disabled – immediate sending of small packets
- Keep-Alive HTTP/1.1 keep-alive, idle timeout 500ms
Type System
Primitive Types
| Tinox Type | Bit | LLVM-IR | Description |
|---|---|---|---|
Int8 / Int16 / Int32 / Int64 | 8–64 | i8–i64 | Signed integers |
UInt8 / UInt16 / UInt32 / UInt64 | 8–64 | i8–i64 | Unsigned integers |
Float32 / Float64 | 32/64 | float/double | IEEE 754 floating-point numbers |
Bool | 1 | i1 | Boolean value |
Char | 32 | i32 | Unicode code point |
String | – | i8* | UTF-8 string (C string) |
Nothing | – | void | No return value (equivalent to void) |
Never | – | void | Function never returns (throw, exit) |
Composite Types
let numbers: List<Int64> = [1, 2, 3]; // Array
let ages: Map<String, Int32> = @{"Alice" => 30}; // Map/Dictionary
let pair: (Int32, String) = (42, "hello"); // Tuple
let name: String? = null; // Nullable
let fn add: fn(Int32, Int32) -> Int32 = |a, b| a + b; // Function type
Generics
Tinox supports parametric polymorphism via monomorphization: a separate function is generated for each concrete type – like C++ templates or Rust. There is no runtime overhead from type erasure (as in Java).
fn max<T>(a: T, b: T) -> T
{
if a > b { return a; }
return b;
}
class Box<T>
{
var value: T;
}
Object Orientation
Tinox is a hybrid language – object-oriented with classes, inheritance, and interfaces, but procedural free functions and functional concepts (lambdas, higher-order functions) are also possible.
Classes
class Animal
{
var name: String;
var age: Int32;
fn speak() -> String // instance method
{
return "...";
}
fnc create(name: String) -> Animal // static method (fnc)
{
return Animal { name: name, age: 0 };
}
}
class Dog extends Animal // inheritance
{
fn speak() -> String
{
return "Wuff!";
}
}
Interfaces & Polymorphism
interface Serializable
{
fn serialize() -> String;
}
class User implements Serializable
{
var name: String;
fn serialize() -> String
{
return "User:" + this.name;
}
}
Interface dispatch is implemented via vtables – identical to C++ virtual functions. Every object that implements an interface receives a pointer to a table of function pointers.
Keyword Overview
| Keyword | Meaning |
|---|---|
fn | Instance method (has implicit this) |
fnc | Static method (no this, called via class name) |
let | Immutable local variable |
var | Mutable local variable or field |
class | Class declaration |
interface | Interface declaration (like Java interface) |
enum | Enumeration type |
extends | Single inheritance |
implements | Interface implementation |
spawn | Start a new thread |
await | Wait for thread result |
defer | Execute code when leaving the scope |
match | Pattern matching expression |
Annotations
The annotation system is one of the core strengths of Tinox. Annotations control the behavior of the compiler, the HTTP server, and future frameworks – similar to Java/Spring or Quarkus.
HTTP REST Annotations
@GET
@Path("/users/:id")
@Produces(MediaType.APPLICATION_JSON)
@Auth("bearer")
@StatusCode(200)
fn getUser(ctx: HttpContext) -> Nothing { ... }
Serialization
@JsonSerializable
class User
{
var id: Int64;
@JsonField("first_name") // Overrides the JSON key
var firstName: String;
@Sensitive // Output as "***" in logs
var password: String;
@DoNotSerialize // Excluded from JSON
var internalToken: String;
}
All Built-in Annotations
| Annotation | Target | Description |
|---|---|---|
@GET @POST @PUT @PATCH @DELETE | Method | HTTP verb for REST route |
@Path("...") | Class / Method | URL path (supports :param) |
@Produces(MediaType.*) | Method | Response content type |
@Consumes(MediaType.*) | Method | Request content type |
@StatusCode(200) | Method | Default HTTP status code |
@Auth("bearer") | Class / Method | Authentication required |
@JsonSerializable | Class | Generates toJson()/fromJson() |
@JsonField("name") | Field | Alternative JSON key |
@Sensitive | Field | Completely masked in logs |
@Masked | Field | Partially masked in logs |
@DoNotSerialize | Field | Exclude field from JSON |
@Log | Class | Automatically inject log field |
@Inject | Field | Dependency injection field |
@Config("key") | Field | Value from application.properties |
@ApplicationComponent | Class | Singleton scope for DI |
@HttpRequestScoped | Class | New object per HTTP request |
@Test("desc") | Method | Unit test (tinox test) |
@inline | Function / Method | Forced LLVM inlining |
@deprecated("msg") | All | Deprecation warning |
@Command("name") | Class | CLI command |
@annotation | Class | Define custom annotation |
@WebsocketEndpoint("/path"[, port]) | Class | WebSocket: generates an accept/message loop as main (only with exactly one endpoint class and no own main) |
@OnOpen | Method | WebSocket: called on new connection, signature fn(conn: Int64) -> Nothing |
@OnMessage | Method | WebSocket: called per text message, signature fn(conn: Int64, msg: String) -> Nothing. AMQP-1.0: called per received message, signature fn(msg: Amqp10Message) -> Nothing. AMQP-0-9-1: called per received message, signature fn(msg: AmqpMessage091) -> Nothing |
@OnClose | Method | WebSocket: called when the connection ends, signature fn(conn: Int64) -> Nothing |
@Amqp10Consumer(host, port, user, pass, address) | Class | AMQP-1.0: generates a connect/attach/receive loop as main (only with exactly one consumer class and no own main) |
@Amqp091Consumer(host, port, vhost, user, pass, queue) | Class | AMQP-0-9-1: generates a connect/open/qos/consume/receive loop as main (only with exactly one consumer class and no own main); prefetch is fixed at 1 in v1 |
Standard Library (tinox-core)
The standard library consists of over 55 modules written in Tinox itself.
Import: import tinox.core.<module>;
HTTP & Web
http_server, rest, rest_framework,
http, websocket – complete HTTP/1.1 server with
routing, middleware, and content negotiation, plus a WebSocket server per
RFC 6455.
Messaging
amqp091 – AMQP-0-9-1 client (no broker) for
message-queue brokers like RabbitMQ: connect, channel, publish/consume.
amqp10 – standalone AMQP-1.0 client with a
connection/session/link hierarchy and credit-based flow control.
Data Formats
json, xml, csv, toml,
ini, base64, hex – serialization and
deserialization of common formats.
Mathematics
math, mathf, decimal,
complex – trigonometric functions, high-precision arithmetic,
complex numbers.
Collections
array, sort, set, queue,
stack, heap, graph, trie
– algorithms and data structures.
Strings & Regex
string, regex, fmt, tpl
– string manipulation, regular expressions, formatting, templating.
I/O & System
io, fs, env, process,
socket – file system, environment variables, processes,
low-level networking.
Concurrency
cron, events, pool,
semaphore, ratelimit, cache
– scheduling, event bus, thread pool, synchronization.
Cryptography
crypto, jwt, bcrypt
– hashing, JSON Web Tokens, password hashing.
LSP – Basics
The Language Server Protocol (LSP) is an open protocol developed by Microsoft in 2016 that standardizes the communication between a language server (which understands the programming language) and an editor (which shows the user interface).
The Problem Before LSP
Previously, every IDE had to implement its own plugins for every language: Eclipse plugin for Java, VS Code plugin for Java, IntelliJ plugin for Java – all separate implementations of the same logic. With n languages and m editors, n × m integrations were created.
LSP Solution: n + m Instead of n × m
With LSP you write one language server, and every LSP-compatible editor uses it automatically. The language server communicates via JSON-RPC over stdio with the editor.
Editor (Eclipse, VS Code, Neovim …)
│
│ JSON-RPC via stdin/stdout
▼
Language Server (tinox-lsp Binary)
│
│ Analyzes .tnx files
▼
Lex → Parse → Typecheck → Responses to editor
Important LSP Methods
| LSP Method | What the editor sends | What the server responds |
|---|---|---|
textDocument/hover | Cursor position | Markdown with type information |
textDocument/completion | Cursor position after . | List of completion suggestions |
textDocument/definition | Cursor on identifier | File + line of the definition |
textDocument/documentSymbol | Document URI | Hierarchical symbol list (Outline) |
textDocument/publishDiagnostics | – (server push) | Error positions + messages |
textDocument/didChange | New file content | Triggers re-analysis |
Tinox LSP Server
tinox-lsp is a complete LSP server implemented with
tower-lsp 0.20 (Rust) and Tokio as asynchronous
runtime.
Embedded Standard Library
All 26+ standard library modules are embedded directly into the LSP binary
using include_str!(). The server requires no installation
or configuration of file paths – it works immediately after copying
the binary.
Import Resolution for Diagnostics
When a .tnx file is opened, the LSP server automatically
resolves import declarations and loads the corresponding stdlib ASTs as
prelude. This way Json.parse() or HttpContext
are not marked as "undefined".
Dot Completion for Classes
When the user types Json.:
- The editor sends
textDocument/completionwith the offset after the dot - The LSP extracts the dot chain (
["Json"]) - It checks whether
Jsonis a variable (instance) or a class name - If class name: directly load the methods of
Jsonfrom the registry - Static methods (
fnc) are displayed with the hintfnc parse(...)
Technical Details
| Property | Value |
|---|---|
| Protocol | Language Server Protocol 3.17 (JSON-RPC via stdio) |
| Async Runtime | Tokio 1.x (multi-threaded) |
| Document Storage | DashMap (lock-free concurrent HashMap) |
| Stdlib embedded | Yes, via include_str!() at compile time |
| Diagnostics Mode | Full re-parse + typecheck bei jedem save/change |
| Supported Editors | Eclipse (LSP4E), VS Code, Neovim, Emacs, … |
Eclipse Plugin
The Eclipse plugin tinox-eclipse connects the Eclipse IDE with the
tinox-lsp language server via the LSP4E framework –
the LSP client for Eclipse.
Features
Real-Time Error Highlighting
Syntax errors and type errors are underlined directly in the editor as soon as the file is saved.
Hover Information
Mouse over a function or variable: type signature and documentation appear in the tooltip.
Go to Definition
Ctrl+click or F3 jumps to the definition of a class, method, or function.
Code Completion
Ctrl+Space or after typing a dot, method and field suggestions appear.
Outline View
The Eclipse Outline View shows all classes, methods, and fields of the current file hierarchically.
Run (Ctrl+F11)
Direct compilation and execution of the current .tnx file
from within Eclipse.
Architecture
The plugin is an OSGi bundle that registers the
.tnx file extension via plugin.xml and instructs LSP4E to start the tinox-lsp process when
.tnx files are opened. Communication runs over stdin/stdout in JSON-RPC.
CLI Tools
The tinox binary is the central tool for all
development tasks:
| Command | Description |
|---|---|
tinox new <name> | Scaffold new project (folder structure, tinox.toml, main.tnx) |
tinox build <file> | Compile to native binary (release: -O3 + LTO) |
tinox run <file> | Compile and run immediately |
tinox dev <file> | Hot-reload mode: automatically recompile on file change |
tinox check <file> | Type check only, no binary generated |
tinox test <file> | Run all @Test-annotated methods |
tinox fmt <file> | Format source code (AST → formatted text) |
tinox doc | Generate HTML documentation from docstrings |
tinox repl | Interactive REPL session |
tinox add … | Add package dependency |
tinox install | Download and install dependencies |
HTTP Server & Performance
The integrated HTTP server is optimized for maximum throughput at minimal latency. It uses technologies also employed in production-ready systems such as Nginx and Envoy.
epoll – Event Multiplexing
Instead of blocking a thread for each connection, the server registers all
active connections in an epoll file descriptor. One
epoll_wait() call delivers all connections that have readable data
– in a single system call.
// Concept (simplified):
epoll_fd = epoll_create1(0);
// Register all active connections...
while (true)
{
events = epoll_wait(epoll_fd, max_events, 50); // 50ms Timeout
for event in events
{
if event.fd == server_socket { accept(); }
else { handle_request(event.fd); }
}
}
SO_REUSEPORT – Kernel Load Balancing
With SO_REUSEPORT, multiple threads (or processes) can listen
on the same port simultaneously. The Linux kernel automatically distributes incoming connections
to available listeners – without locks, without bottlenecks.
Thread-Local Buffers
Each HTTP worker thread has its own static buffer for request data, response data, and JSON parsing. These buffers are never freed and are reused – this completely eliminates malloc/free in the critical request path.
Benchmark: Tinox vs. Quarkus
A sort REST endpoint was measured under identical conditions against Quarkus (Java, native image):
| Metric | Tinox | Quarkus | Difference |
|---|---|---|---|
| Throughput | 26.700–27.000 req/s | 24.867–25.541 req/s | +5–7% |
| Latency (mean) | 0,44–0,45 ms | 0,52–0,55 ms | ~17–20% better |
| Error rate | 0 % | 0 % | – |
Test conditions: 5,000 requests, concurrency 20, list with 20 numbers per request, Linux x86_64.
Pros & Cons
✓ Advantages
- Very low latency and high throughput thanks to LLVM + epoll
- Boehm GC – automatic memory management without borrow-checker complexity
- Low startup time (<1ms) – ideal for serverless/Lambda
- Java-like syntax – low entry barrier for backend developers
- Annotation-driven REST: minimal boilerplate
- Complete tooling: LSP, Eclipse plugin, REPL, formatter
- Standard library written in the language itself
- Small binary footprint – no framework as runtime dependency
- Embedded stdlib in the LSP – no configuration required
- Cross-platform via LLVM (Linux, macOS, Windows possible)
✗ Disadvantages
- Conservative GC (Boehm) – can be somewhat slower than manual management for pointer-intensive workloads
- Small ecosystem – no mature third-party libraries
- No standardized specification – language changes quickly
- Compiler errors not yet as helpful as in Rust or Go
- No complete module system with real separation between projects
- No borrow-checker safety – use-after-free theoretically possible (but GC prevents leaks)
- Long compile times due to LLVM (for large projects)
- No Windows support (epoll is Linux-specific)
- No stable releases yet – pre-1.0
- Thin community and documentation so far
Comparison: Memory Safety
Tinox gives the developer full control over memory – like C –, but does not offer static guarantees like Rust's borrow checker. This means:
| Language | Memory Model | Safety | Performance |
|---|---|---|---|
| Java / Kotlin | GC | High (no UAF) | GC pauses |
| Rust | Ownership + Borrow-Checker | Very high (compile-time) | Maximum |
| Go | GC (concurrent) | High | Good |
| Tinox | Boehm GC (conservative) | GC prevents leaks | Very high |
| C/C++ | Manual | Developer responsibility | Maximum |
Use Cases
High-Performance REST APIs
Microservices with very low latency requirements (trading, real-time systems). Annotation-driven routing, no framework overhead.
Serverless / Edge Computing
Cold start <1ms thanks to native binary without JVM or Node.js. Ideal for AWS Lambda, Cloudflare Workers (native mode), edge functions.
System Tools & CLIs
With @Command/@Option/@Argument
CLI tools can be developed with minimal effort. Native binary without
runtime dependencies.
Data Processing Pipelines
Batch processing of large data volumes with automatic GC – no manual memory management required, yet high performance.
Security-Critical Services
Deterministic runtime behavior through conservative GC with predictable behavior. Annotations for sensitivity and masking built in.
Teaching & Research
Small, readable compiler codebase. Ideal for learning compiler construction, LLVM IR, and LSP development.
Less Suitable For…
- Frontend development (no browser target, no WASM focus)
- Data science / ML (no NumPy equivalent, no notebook integration)
- Mobile apps (no iOS/Android framework)
- Projects that require a huge package ecosystem (npm, Maven)
Comparison with Other Languages
| Feature | Tinox | Java / Quarkus | Go | Rust | C |
|---|---|---|---|---|---|
| Compilation | AOT → native | JVM / AOT (native image) | AOT → native | AOT → native | AOT → nativ |
| Garbage Collector | Yes (Boehm GC) | Yes (G1GC, ZGC) | Yes (concurrent) | No (Ownership) | No |
| Memory Safety | GC-protected (no leaks) | GC-protected | GC-protected | Compile-time | Manual |
| Syntax Style | Java-like | Java | C-like | C-like | C |
| REST Annotations | Built-in | JAX-RS / Quarkus | No standard | No standard | None |
| Startup Time | <1ms | JVM: 1–3s; Native: ~10ms | ~5ms | <1ms | <1ms |
| IDE Tooling | LSP + Eclipse | Very mature | Good (gopls) | Very good (rust-analyzer) | Medium (clangd) |
| Ecosystem | Small (growing) | Huge (Maven) | Good | Gut (crates.io) | Large (mature) |
| Concurrency | Threads + Channels | Virtual Threads (Java 21) | Goroutines | async/await + Threads | pthreads |
| Backend LLVM | Yes | No (JVM / Graal) | No (custom) | Yes | Optional (clang) |
http_server
import tinox.core.http_server;
HTTP/1.1 server implementation for building RESTful APIs. Supports
route registration for all common HTTP methods (GET, POST, PUT, PATCH,
DELETE), middleware chains, and automatic parsing of requests and
responses. Path parameters are declared via the :param syntax,
wildcard segments via *.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
HttpServer::new | fn new(port: Int64) -> HttpServer | Creates a new server listening on the specified port. |
HttpServer::get | fn get(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServer | Registers a GET route handler. Returns this (chaining). |
HttpServer::post | fn post(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServer | Registers a POST route handler. |
HttpServer::put | fn put(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServer | Registers a PUT route handler. |
HttpServer::patch | fn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServer | Registers a PATCH route handler. |
HttpServer::delete | fn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> HttpServer | Registers a DELETE route handler. |
HttpServer::use | fn use(mware: fnc(HttpContext) -> Bool) -> HttpServer | Adds a middleware function. If the middleware returns false, the request is aborted. |
HttpServer::listen | fn listen() -> Nothing | Starts the server (blocking call). Only terminates via stop(). |
HttpServer::stop | fn stop() -> Nothing | Stops the server loop. |
HttpServer::handleRequest | fn handleRequest(ctx: HttpContext, clientFd: Int64) -> Nothing | Processes an incoming request through middleware and route matching. |
HttpServer::statusText | fn statusText(code: Int64) -> String | Returns the HTTP status text for a numeric status code (e.g. 200 → "OK"). |
HttpRequest::new | fn new(method: String, path: String, queryString: String, headers: Map<String, String>, body: String) -> HttpRequest | Creates a new HTTP request instance. |
HttpRequest::getHeader | fn getHeader(name: String) -> String | Returns the value of a request header (case-insensitive). |
HttpRequest::getParam | fn getParam(name: String) -> String | Returns a path parameter extracted by :name in the route pattern. |
HttpRequest::getQuery | fn getQuery(name: String) -> String | Returns a query string parameter. |
HttpRequest::json | fn json() -> JsonValue | Parses the request body as JSON. |
HttpRequest::bodyAs | fn bodyAs<T>() -> T | Deserializes the body into an object of type T (must be @JsonSerializable). |
HttpRequest::text | fn text() -> String | Returns the body as a raw string. |
HttpRequest::isContentType | fn isContentType(contentType: String) -> Bool | Checks whether the Content-Type header matches the given value. |
HttpResponse::new | fn new() -> HttpResponse | Creates a new response with default status code 200. |
HttpResponse::status | fn status(code: Int64) -> HttpResponse | Sets the HTTP status code. Returns this (chaining). |
HttpResponse::header | fn header(name: String, value: String) -> HttpResponse | Sets a response header. |
HttpResponse::text | fn text(text: String) -> HttpResponse | Sets the response body as plain text (text/plain). |
HttpResponse::json | fn json(json: String) -> HttpResponse | Sets the response body as a JSON string (application/json). |
HttpResponse::jsonObject | fn jsonObject<T>(obj: T) -> HttpResponse | Serializes a @JsonSerializable object and sets it as the JSON body. |
HttpResponse::html | fn html(html: String) -> HttpResponse | Sets the response body as HTML (text/html). |
HttpResponse::notFound | fn notFound(message: String) -> HttpResponse | Sets status code 404 and an error message. |
HttpResponse::badRequest | fn badRequest(message: String) -> HttpResponse | Sets status code 400 and an error message. |
HttpResponse::internalError | fn internalError(message: String) -> HttpResponse | Sets status code 500 and an error message. |
HttpResponse::created | fn created(json: String) -> HttpResponse | Sets status code 201 with JSON body. |
HttpResponse::noContent | fn noContent() -> HttpResponse | Sets status code 204 (no body). |
HttpResponse::redirect | fn redirect(url: String) -> HttpResponse | Sets a 302 redirect with Location header. |
HttpResponse::cors | fn cors(origin: String) -> HttpResponse | Sets CORS headers for cross-origin requests. |
HttpContext::new | fn new(request: HttpRequest, response: HttpResponse) -> HttpContext | Creates a request context that encapsulates request and response. |
RouteMatcher::matches | fn matches(pattern: String, path: String) -> Bool | Checks whether a path matches a route pattern (incl. :param and *). |
RouteMatcher::extractParams | fn extractParams(pattern: String, path: String) -> Map<String, String> | Extracts path parameters from a concrete path using the pattern. |
QueryString::parse | fn parse(qs: String) -> Map<String, String> | Parses a query string into a key-value map. |
QueryString::get | fn get(qs: String, name: String) -> String | Returns a single query parameter from a raw query string. |
Example
import tinox.core.http_server;
// Auth middleware: checks Authorization header
let authMiddleware: fnc(HttpContext) -> Bool = ctx => {
let token: String = ctx.request.getHeader("Authorization");
if token.len() == 0 {
ctx.response.status(401).text("Unauthorized");
return false;
}
return true;
};
let server: HttpServer = HttpServer::new(8080);
server.use(authMiddleware);
server.get("/hello", ctx => {
ctx.response.text("Hello, World!");
});
server.get("/users/:id", ctx => {
let id: String = ctx.request.getParam("id");
let page: String = ctx.request.getQuery("page");
ctx.response.json("{\"id\": \"" + id + "\", \"page\": \"" + page + "\"}");
});
server.post("/users", ctx => {
let data: JsonValue = ctx.request.json();
ctx.response.status(201).json("{\"created\": true}");
});
server.delete("/users/:id", ctx => {
ctx.response.noContent();
});
server.listen();
http2_server
import tinox.core.http2_server;
Complete HTTP/2 server implementation according to RFC 7540 with binary
framing, HPACK header compression, stream multiplexing, and flow control.
The routing API is identical to http_server, so existing
handlers can be reused without modification. Each connection goes through
the complete HTTP/2 connection sequence: preface validation, SETTINGS exchange,
frame processing loop, and GOAWAY shutdown.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Http2Server::new | fn new(port: Int64) -> Http2Server | Creates a new HTTP/2 server on the specified port. |
Http2Server::get | fn get(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2Server | Registers a GET route handler. |
Http2Server::post | fn post(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2Server | Registers a POST route handler. |
Http2Server::put | fn put(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2Server | Registers a PUT route handler. |
Http2Server::patch | fn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2Server | Registers a PATCH route handler. |
Http2Server::delete | fn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> Http2Server | Registers a DELETE route handler. |
Http2Server::use | fn use(mware: fnc(HttpContext) -> Bool) -> Http2Server | Adds a middleware function. |
Http2Server::listen | fn listen() -> Nothing | Starts the server (blocking). Accepts and processes HTTP/2 connections. |
Http2Server::stop | fn stop() -> Nothing | Stops the server loop. |
Http2Server::handleConnection | fn handleConnection(fd: Int64) -> Nothing | Manages a single HTTP/2 connection completely: preface, SETTINGS, frame loop, GOAWAY. |
Http2Server::readFrame | fn readFrame(conn: Http2Conn) -> Http2Frame | Reads an HTTP/2 frame (9-byte header + payload) from the client. |
Http2Server::writeFrame | fn writeFrame(conn: Http2Conn, frame: Http2Frame) -> Nothing | Sends an HTTP/2 frame to the client. |
Http2Server::sendServerSettings | fn sendServerSettings(conn: Http2Conn) -> Nothing | Sends the server's initial SETTINGS frame (push disabled, max. 128 streams). |
Http2Server::sendGoaway | fn sendGoaway(conn: Http2Conn, errorCode: Int64) -> Nothing | Sends a GOAWAY frame and closes the connection. |
Http2Server::sendRstStream | fn sendRstStream(conn: Http2Conn, streamId: Int64, errorCode: Int64) -> Nothing | Sends an RST_STREAM frame to abort a single stream. |
Http2Server::processHeaders | fn processHeaders(conn: Http2Conn, frame: Http2Frame) -> Nothing | Processes a HEADERS frame and accumulates the HPACK header block. |
Http2Server::processData | fn processData(conn: Http2Conn, frame: Http2Frame) -> Nothing | Processes a DATA frame and accumulates body bytes. |
Http2Server::dispatchStream | fn dispatchStream(conn: Http2Conn, stream: Http2Stream) -> Nothing | Decodes a complete stream into an HttpContext and runs middleware and route matching. |
Http2Server::sendResponse | fn sendResponse(conn: Http2Conn, streamId: Int64, response: HttpResponse) -> Nothing | Encodes an HttpResponse as HEADERS and DATA frames and sends it. |
Http2Frame::new | fn new(type: Int64, flags: Int64, streamId: Int64, payload: List<Int64>) -> Http2Frame | Creates an HTTP/2 frame with header fields and payload. |
Http2Conn::new | fn new(handle: Int64) -> Http2Conn | Creates the connection state for an HTTP/2 connection incl. HPACK tables. |
Http2Stream::new | fn new(id: Int64) -> Http2Stream | Creates a new stream state for an HTTP/2 stream. |
Http2FrameType::DATA | fn DATA() -> Int64 | Frame type constant: DATA (0x0). |
Http2FrameType::HEADERS | fn HEADERS() -> Int64 | Frame type constant: HEADERS (0x1). |
Http2FrameType::SETTINGS | fn SETTINGS() -> Int64 | Frame type constant: SETTINGS (0x4). |
Http2FrameType::GOAWAY | fn GOAWAY() -> Int64 | Frame type constant: GOAWAY (0x7). |
Http2Error::NO_ERROR | fn NO_ERROR() -> Int64 | Error code: No error (0x0). |
Http2Error::PROTOCOL_ERROR | fn PROTOCOL_ERROR() -> Int64 | Error code: Protocol error (0x1). |
Example
import tinox.core.http2_server;
let server: Http2Server = Http2Server::new(8443);
// Logging-Middleware
server.use(ctx => {
print("Request: " + ctx.request.method + " " + ctx.request.path);
return true;
});
server.get("/hello", ctx => {
ctx.response.text("Hello via HTTP/2!");
});
server.post("/api/data", ctx => {
let body: JsonValue = ctx.request.json();
ctx.response.status(201).json("{\"ok\": true}");
});
server.get("/users/:id", ctx => {
let id: String = ctx.request.getParam("id");
ctx.response.json("{\"id\": \"" + id + "\"}");
});
server.delete("/users/:id", ctx => {
ctx.response.noContent();
});
// Start blocking — processes HTTP/2 connections with
// full RFC-7540 framing, HPACK compression, and stream multiplexing
server.listen();
http3_server
import tinox.core.http3_server;
HTTP/3 (RFC 9114) over QUIC (RFC 9000). Unlike http_server
(protocol parsed entirely in C) and http2_server (framing/
HPACK hand-rolled in pure Tinox), this module can't push protocol logic
into Tinox: QUIC transport (loss recovery, congestion control, AEAD) and
HTTP/3 framing + QPACK are implemented by the
ngtcp2 and
nghttp3 C libraries,
whose callback-table APIs can't invoke back into a Tinox closure
mid-callback. All QUIC/HTTP-3/QPACK state therefore lives in
runtime.c behind opaque Int64 handles (the
same pattern as the existing TLS connection handling) — this file only
registers routes and pumps the native event loop, reusing the same
Route/RouteMatcher/HttpRequest/
HttpResponse/HttpContext API as
http_server/http2_server unmodified.
TLS is mandatory (QUIC has no plaintext mode) — certificate/key go
directly to the constructor; unlike HttpServer there is no
separate listen()/listenTls() split.
Build flag: requires the compiler to be run with
TINOX_HTTP3=1 (opt-in, default OFF — unlike
TINOX_TLS, which defaults ON: OpenSSL is nearly universally
installed, ngtcp2/nghttp3 far less so; defaulting this on would break
tinox build with a compile error on any system lacking
them, instead of a clean runtime message). Also requires TLS to be
enabled (ngtcp2_crypto_ossl needs OpenSSL underneath).
Known limitation (0-RTT): enableEarlyData(),
HttpRequest.wasEarlyData, and the 425-Too-Early policy for
non-idempotent early-data requests are wired end-to-end, but
SSL_CTX_set_max_early_data itself is deliberately left unset
under this OpenSSL 3.6.3 + ngtcp2_crypto_ossl 1.25.0
combination (setting it reproducibly caused a stall right after the TLS
handshake completed; root cause not isolated in the time available). As
a result the TLS layer never actually accepts 0-RTT data — a client
attempting early data transparently falls back to a normal 1-RTT round
trip, so wasEarlyData is currently always false.
Not a crash, just an inactive feature for now.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Http3Server::new | fn new(port: Int64, certPath: String, keyPath: String) -> Http3Server | Creates a new HTTP/3 server. TLS certificate/key are mandatory parameters. |
Http3Server::get | fn get(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3Server | Registers a GET route handler. |
Http3Server::post | fn post(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3Server | Registers a POST route handler. |
Http3Server::put | fn put(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3Server | Registers a PUT route handler. |
Http3Server::patch | fn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3Server | Registers a PATCH route handler. |
Http3Server::delete | fn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> Http3Server | Registers a DELETE route handler. |
Http3Server::use | fn use(mware: fnc(HttpContext) -> Bool) -> Http3Server | Adds a middleware function. |
Http3Server::requireAddressValidation | fn requireAddressValidation(v: Bool) -> Http3Server | Controls the QUIC Retry mechanism (RFC 9000 §8.1, default true). false saves a round trip in local dev loops. |
Http3Server::enableEarlyData | fn enableEarlyData(maxSize: Int64) -> Http3Server | Opts into TLS 1.3 0-RTT (default off). See the known limitation above. |
Http3Server::listen | fn listen() -> Nothing | Binds the UDP socket and starts the pump loop (blocking). |
Http3Server::stop | fn stop() -> Nothing | Stops the server loop. |
HttpRequest.wasEarlyData | var wasEarlyData: Bool | New field on HttpRequest (see http_server): true if the request arrived as TLS 1.3 0-RTT early data. Always false for HttpServer/Http2Server. |
Example
import tinox.core.http3_server;
let server: Http3Server = Http3Server::new(8443, "cert.pem", "key.pem");
server.get("/hello", ctx => {
ctx.response.text("Hello via HTTP/3!");
});
server.post("/echo", ctx => {
ctx.response.text("echo:" + ctx.request.body);
});
// Start blocking -- QUIC handshake, HTTP/3 framing, and QPACK are
// handled entirely by ngtcp2/nghttp3.
server.listen();
Build/run: TINOX_HTTP3=1 tinox run server.tnx, then e.g.
curl --http3-only -k https://localhost:8443/hello (tested
against real curl 8.21.0, linked against the same ngtcp2/nghttp3
versions).
A complete example (a JSON REST API with GET/POST/PUT/PATCH/DELETE)
lives at examples/http3_rest_api.
@Http3RestController — declarative instead of the fluent API
The same @GET/@POST/@PUT/
@PATCH/@DELETE/@Path/
@StatusCode/@Produces/@Consumes/
@Auth/@OIDCRolesAllowed annotations already
used by the plain (TCP) REST auto-server (module rest)
also work for HTTP/3 — with one additional class annotation,
@Http3RestController(port, certPath, keyPath), the
compiler generates a complete Http3Server::new()/
.get()/.post()/…/.listen() `main`,
exactly the wiring the hand-written fluent-API version above writes
out by hand — not the GC-crash-prone TCP auto-server
(tinox_HttpServer_listen, issue #140), but
Http3Server directly.
At most one @Http3RestController class per program (hard
error otherwise); every @GET/… route in the whole program
ends up on that one server. Cannot be combined with
@WebsocketEndpoint/@Amqp10Consumer/
@Amqp091Consumer (each generates its own auto-run `main`).
Since the compiler allocates a fresh, zeroed controller instance per
request by default, a stateful controller additionally needs
@ApplicationComponent (a reused singleton instance via
{Class}_di_get()) — see
examples/http3_rest_api/src/TaskController.tnx for the
full example and details.
import tinox.core.http3_server;
@ApplicationComponent
@Http3RestController(8843, "cert.pem", "key.pem")
class TaskController
{
var tasks: List<Task>;
@GET
@Path("/tasks")
fn listTasks(ctx: HttpContext) -> Nothing
{
ctx.response.json(Json::serialize(this.tasks));
}
}
websocket
import tinox.core.websocket;
WebSocket server per RFC 6455, built on the HTTP server's conn-handle
architecture. The textual handshake (upgrade request,
Sec-WebSocket-Accept) runs over the existing C-string paths,
the binary frames over length-based byte primitives so NUL bytes and
masking are handled correctly. v1 is deliberately an explicit polling-loop
API (no lambda handler) and serves one connection at a time per server
(no epoll for WS).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
WsServer::listen | fn listen(port: Int64) -> Int64 | Opens the server socket. Returns the server handle (< 0 on bind failure, e.g. port already in use). |
WsServer::accept | fn accept(srv: Int64) -> Int64 | Accepts the next connection and performs the RFC 6455 handshake. Returns the conn handle, or -1 if the handshake fails (the connection is already closed in that case). |
WsServer::listenTls | fn listenTls(port: Int64, certPath: String, keyPath: String) -> Int64 | wss:// variant of listen. OpenSSL is linked by default; when built with TINOX_TLS=0 this function returns -1 with a stderr diagnostic instead of a link error. |
WsServer::acceptTls | fn acceptTls(srv: Int64) -> Int64 | wss:// variant of accept: TLS handshake followed by the same RFC 6455 handshake over the same conn handle. |
Ws::readMessage | fn readMessage(conn: Int64) -> WsFrame | Reads the next data message (blocking). Automatically answers Ping with Pong and acknowledges Close with Close (returns the close frame so the caller can end its loop). |
Ws::sendText | fn sendText(conn: Int64, s: String) -> Nothing | Sends a text frame (opcode 1). |
Ws::sendBinary | fn sendBinary(conn: Int64, bytes: List<Int64>) -> Nothing | Sends a binary frame (opcode 2), one byte per list slot. |
Ws::sendPing | fn sendPing(conn: Int64) -> Nothing | Sends a ping frame. |
Ws::sendClose | fn sendClose(conn: Int64, code: Int64) -> Nothing | Sends a close frame with the given status code. |
Ws::close | fn close(conn: Int64) -> Nothing | Closes the underlying connection. |
Ws::text | fn text(f: WsFrame) -> String | Converts a frame's payload to a string (byte-based). |
WsFrame | { fin: Bool, opcode: Int64, payload: List<Int64>, rsv1: Bool } | A frame that was read. Opcode as per RFC 6455 (1 text, 2 binary, 8 close, 9 ping, 10 pong); additionally -1 = connection closed/EOF, -2 = protocol error (the connection has already been answered with a close). rsv1 is only relevant internally (permessage-deflate reassembly) — payload is already decompressed by the time readMessage returns it. |
Example
import tinox.core.websocket;
let srv: Int64 = WsServer::listen(8790);
while true {
let conn: Int64 = WsServer::accept(srv);
if conn <= 0 { continue; }
while true {
let f: WsFrame = Ws::readMessage(conn);
if f.opcode == 1 {
Ws::sendText(conn, "echo: " + Ws::text(f));
continue;
}
if f.opcode == 2 {
Ws::sendBinary(conn, f.payload);
continue;
}
break; // Close (8), EOF (-1), or protocol error (-2)
}
Ws::close(conn);
}
Deliberate v1 gaps: payload cap
16 MB (also applies to a reassembled fragmented message, see below,
and to permessage-deflate's decompressed output, see below).
wss:// (TLS) is supported, see listenTls/acceptTls
above. Fragmented messages (continuation frames, §5.4) are transparently
reassembled by readMessage; outgoing connections as a client
are covered by WsClient, see below — WsClient
deliberately doesn't offer permessage-deflate in v1 (see the
permessage-deflate section below).
permessage-deflate (RFC 7692)
If a client offers Sec-WebSocket-Extensions: permessage-deflate
in its handshake request, Ws::handshake (and therefore
WsServer::accept/acceptTls) always
accepts it with client_no_context_takeover;
server_no_context_takeover (RFC 7692 §7.1.1 lets the server include
these parameters unilaterally in its response) — every message gets a
fresh DEFLATE context this way, so there's no persistent per-connection
compression state to manage or free on close. Fully transparent to
callers: readMessage already returns decompressed payload,
sendText/sendBinary compress automatically once
the connection has negotiated the extension — no API change, no separate
*Compressed counterpart. If the client doesn't offer the
extension, the connection behaves exactly as before this feature (no
compression).
WsClient (outgoing connections) deliberately does not offer
the extension in v1 — a real third-party server could respond with
context takeover (RFC 7692's default), which would require persistent
compression state across multiple messages; that's the same
architectural tension that originally kept permessage-deflate out of
this module, just from the client instead of the server side.
wss:// (TLS)
listenTls/acceptTls replace listen/accept
1:1 — the rest of the code (handshake, readMessage, sendText, etc.)
stays unchanged, because the underlying conn-handle primitives are already
transparent for TLS and plaintext connections:
import tinox.core.websocket;
let srv = WsServer::listenTls(8791, "cert.pem", "key.pem");
if srv < 0 { return 1; } // port in use, built with TINOX_TLS=0, or invalid cert/key
let conn = WsServer::acceptTls(srv); // TLS + WS handshake
OpenSSL is linked by default, just like with
HttpServer::listenTls — no extra flag needed. Opt out via
TINOX_TLS=0 if no OpenSSL is available for building.
Annotation-driven variant
Alternatively, the compiler generates the entire loop itself — see
Annotations (@WebsocketEndpoint/@OnOpen/
@OnMessage/@OnClose). Only applies if the file defines no
main of its own and contains exactly one @WebsocketEndpoint
class (more than one is a compile error, not silently ignored):
import tinox.core.websocket;
@WebsocketEndpoint("/echo", 8793)
class EchoEndpoint
{
@OnMessage
fn onMessage(conn: Int64, msg: String) -> Nothing
{
Ws::sendText(conn, "echo: " + msg);
}
}
WsClient (outgoing connections)
WsClient connects outward to a WS server, the
reverse of WsServer accepting incoming connections. Role
reversal versus the server API (RFC 6455 §5.1): outgoing frames are
masked, incoming ones from the server must be unmasked — both directions
share the same frame codec as Ws/WsServer, no
separate implementation.
| Class / Method | Signature | Description |
|---|---|---|
WsClient::connect | fn connect(host: String, port: Int64, path: String) -> Int64 | Opens a TCP connection and performs the client-side handshake (its own Sec-WebSocket-Key, verifies the server's response including the Accept header). Returns the conn handle, or -1 (the connection is already closed in that case). |
WsClient::readMessage | fn readMessage(conn: Int64) -> WsFrame | Like Ws::readMessage (including automatic Ping→Pong, close acknowledgement, fragment reassembly), just with the masking direction reversed. |
WsClient::readFrame | fn readFrame(conn: Int64) -> WsFrame | Reads exactly one raw frame (no automatic control-frame handling) — like Ws::readFrame, reversed for the client role. |
WsClient::sendText / sendBinary / sendPing / sendClose | same as Ws | Same as the identically named Ws methods, but send masked frames (required for a client). |
WsClient::close | fn close(conn: Int64) -> Nothing | Closes the underlying connection. |
import tinox.core.websocket;
let conn: Int64 = WsClient::connect("example.org", 8080, "/chat");
if conn < 0 { return 1; } // connection/handshake failed
WsClient::sendText(conn, "hello");
let f: WsFrame = WsClient::readMessage(conn);
println(WsClient::text(f));
WsClient::sendClose(conn, 1000);
WsClient::close(conn);
sse
import tinox.core.sse;
Server-Sent Events (text/event-stream, the W3C EventSource protocol)
— lightweight, one-way server push over plain HTTP for the common case (live
dashboards, progress/notification streams) where a full WebSocket handshake and
binary framing (websocket) is more than what's needed. Follows the
same conn-handle-hijack shape as WsServer/Ws:
SseServer::accept sends the initial 200 response with
Content-Type: text/event-stream, then hands back a raw conn handle
for the caller to push events over directly with Sse::sendEvent,
instead of going through HttpServer's one-shot buffered
request/response model (which has no incremental/chunked-write API to hold a
connection open and write to it over time).
v1 scope: no Last-Event-ID/reconnection-resume handling and no
retry: field (client reconnection-interval hint) — every reconnect
starts a fresh stream from the caller's perspective.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
SseServer::listen |
fn listen(port: Int64) -> Int64 |
Opens a TCP listener on the given port. |
SseServer::accept |
fn accept(srv: Int64) -> Int64 |
Accepts the next connection and sends the SSE handshake response
(200, Content-Type: text/event-stream, no framing).
Returns the conn handle, or -1 if the request was
unreadable/empty (connection already closed in that case). |
SseServer::listenTls |
fn listenTls(port: Int64, certPath: String, keyPath: String) -> Int64 |
https:// variant of listen. |
SseServer::acceptTls |
fn acceptTls(srv: Int64) -> Int64 |
TLS variant of accept. |
Sse::sendEvent |
fn sendEvent(conn: Int64, event: String, data: String) -> Nothing |
Sends one SSE event. A multi-line data gets one
data: field per line, per the EventSource spec. |
Sse::sendComment |
fn sendComment(conn: Int64) -> Nothing |
Sends an SSE comment line (: keepalive) — ignored by
EventSource clients, but useful to keep proxies/load balancers from timing
out an otherwise-idle connection. |
Sse::close |
fn close(conn: Int64) -> Nothing |
Closes the connection. |
Example
import tinox.core.sse;
let srv: Int64 = SseServer::listen(8080);
let conn: Int64 = SseServer::accept(srv);
if conn > 0
{
Sse::sendEvent(conn, "message", "hello");
Sse::sendComment(conn); // keepalive
Sse::close(conn);
}
amqp091
import tinox.core.amqp091;
AMQP-0-9-1 client (no broker) for message-queue brokers
like RabbitMQ. Built on the same binary-safe conn-handle architecture as
the WebSocket server. v1: a fixed channel per connection, an explicit
publish/consume API (no lambda handler). amqps:// (TLS) is
supported via connectTls. AMQP 1.0 is a separate, later
module with a completely different type system.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
AmqpConnection091::connect | fn connect(host: String, port: Int64, vhost: String, user: String, pass: String) -> AmqpConnection091 | Connects, performs SASL PLAIN auth + negotiation. .conn <= 0 + .errorMessage on any failure (auth failure, unknown vhost, connection error). |
AmqpConnection091::connectTls | fn connectTls(host: String, port: Int64, vhost: String, user: String, pass: String, verify: Bool) -> AmqpConnection091 | amqps:// variant of connect (broker default port 5671). OpenSSL is linked by default. verify=true checks the certificate chain + hostname against the system CA stores, verify=false is a deliberate opt-out for self-signed test certificates. |
AmqpConnection091::close | fn close() -> Nothing | Closes the connection per spec (connection.close, waits for close-ok). |
AmqpChannel091::open | fn open(connection: AmqpConnection091) -> AmqpChannel091 | Opens a new channel on the connection (sequential channel id, callable as many times as needed — multiple independent channels per connection, see below). |
AmqpChannel091::declareQueue | fn declareQueue(name: String, durable: Bool, exclusive: Bool, autoDelete: Bool) -> String | Declares a queue, returns the (possibly server-generated) name, or "" on failure. |
AmqpChannel091::declareExchange | fn declareExchange(name: String, exchangeType: String, durable: Bool, autoDelete: Bool) -> Bool | Declares an exchange (exchangeType: "direct", "fanout", "topic", "headers" — validated broker-side). true on success, false + errorMessage on failure. |
AmqpChannel091::bindQueue | fn bindQueue(queue: String, exchange: String, routingKey: String) -> Bool | Binds a queue to an exchange (the default exchange, one created via declareExchange(), or a broker-predefined one). Binding to the default exchange ("") is forbidden by spec (the broker responds with an error) — queues are implicitly bound there under their own name. |
AmqpChannel091::qos | fn qos(prefetchCount: Int64) -> Bool | Sets the prefetch limit before consume(). |
AmqpChannel091::confirmSelect | fn confirmSelect() -> Bool | Enables publisher confirms (RabbitMQ extension, class 85). Must be called before the first publish(), whose return value otherwise stays 0 forever. |
AmqpChannel091::publish | fn publish(exchange: String, routingKey: String, body: List<Int64>, contentType: String) -> Int64 | Publishes a message (asynchronous per spec). Return value: the assigned delivery tag (sequential starting at 1) if confirmSelect() was called beforehand, otherwise 0. The body is automatically split across multiple frames at frameMax. |
AmqpChannel091::waitForConfirm | fn waitForConfirm() -> AmqpConfirmResult091 | Blocking pull of the next publisher confirm (requires confirmSelect()). Not safe to mix with nextMessage() on a channel that's consuming at the same time (no frame dispatch by type in v1). |
AmqpChannel091::consume | fn consume(queue: String) -> String | Registers the client as a consumer, returns the consumer tag, or "" on failure. |
AmqpChannel091::nextMessage | fn nextMessage() -> AmqpMessage091 | Blocking pull of the next message (requires consume()). Automatically reassembles body frames. |
AmqpChannel091::ack | fn ack(deliveryTag: Int64) -> Nothing | Acknowledges a message received via nextMessage(). |
AmqpMessage091 | { deliveryTag: Int64, exchange: String, routingKey: String, contentType: String, body: List<Int64>, ok: Bool, errorMessage: String } | A message received via nextMessage(). ok == false on any failure, errorMessage describes the cause. |
AmqpConfirmResult091 | { deliveryTag: Int64, multiple: Bool, ack: Bool, ok: Bool, errorMessage: String } | A confirm received via waitForConfirm(). ack == false means basic.nack (the broker couldn't process the message), multiple == true confirms every tag up to and including deliveryTag at once. ok == false on any failure, errorMessage describes the cause. |
Example
import tinox.core.amqp091;
let conn = AmqpConnection091::connect("127.0.0.1", 5672, "/", "guest", "guest");
let ch = AmqpChannel091::open(conn);
let queueName = ch.declareQueue("my-queue", true, false, false);
var body: List<Int64> = [];
for i in 0..3 { body.push("abc".charCodeAt(i)); }
ch.publish("", queueName, body, "text/plain");
ch.consume(queueName);
let m = ch.nextMessage();
if m.ok {
ch.ack(m.deliveryTag);
}
conn.close();
amqps:// (TLS)
connectTls replaces connect 1:1 — the rest of
the code stays unchanged, because the whole handshake runs over the same
TLS-/plaintext-transparent conn primitives:
let conn = AmqpConnection091::connectTls("broker.example.com", 5671, "/", "guest", "guest", true);
// verify=true checks the certificate chain + hostname against the system CA stores;
// verify=false is a deliberate opt-out for self-signed test certificates.
OpenSSL is linked by default, just like with
HttpServer::listenTls/WsServer::listenTls — no
extra flag needed. Opt out via TINOX_TLS=0 if no OpenSSL is
available for building.
Heartbeats (§4.2.7) run on a background thread, same explicit opt-in as
amqp10: conn.heartbeat is the interval (seconds)
the broker proposed in connection.tune — informational only
until startHeartbeat is called.
conn.startHeartbeat(20000); // one heartbeat frame every 20s, in the background
// ...
conn.stopHeartbeat(); // or just conn.close(), which stops it for you
Deliberate v1 gaps: no transactions
(tx.select), no auto-reconnect, no dedicated CA-bundle
parameter for connectTls (verifies only against the
system CA stores). AMQP 1.0 is not part of this module.
Multiple channels
AmqpChannel091::open can be called any number of times on
the same AmqpConnection091 — each call gets its own
sequentially assigned channel id and its own frame inbox. A
background task per connection reads every frame off the socket and
routes it to the right inbox by channel id, so a channel with
independent traffic (e.g. an active consumer) doesn't disturb another
channel that's synchronously waiting for its own reply
(declareQueue/qos/consume/...):
let conn = AmqpConnection091::connect("127.0.0.1", 5672, "/", "guest", "guest");
let ch1 = AmqpChannel091::open(conn); // channel id 1
let ch2 = AmqpChannel091::open(conn); // channel id 2, independent of ch1
Within a SINGLE channel, the v1 limitation still applies that frames
aren't dispatched by method type — e.g. consuming and waiting for
publisher confirms on the same channel at the same time is still
unsafe (waitForConfirm could misread a
basic.deliver as a confirm). Use separate channels for
that case.
Publisher confirms
let selected = ch.confirmSelect();
let tag = ch.publish("amq.direct", "rk", body, "text/plain"); // tag == 1, 2, 3, ...
let confirm = ch.waitForConfirm();
if confirm.ok {
if confirm.ack {
// the broker accepted the message with tag confirm.deliveryTag
} else {
// basic.nack -- the broker couldn't process it, consider resending
}
}
Annotation-driven consumer variant
Like WebSocket/AMQP-1.0, the compiler can alternatively generate the
entire connect/open/qos/consume/receive loop itself — see
Annotations (@Amqp091Consumer/
@OnMessage). Only applies if the file defines no own
main and contains exactly one @Amqp091Consumer
class (more than one is a compile error, never silently ignored).
Prefetch is fixed at 1 in v1 (no annotation argument for it). The
handler receives the entire AmqpMessage091 (not an
already-decoded string) — body decoding stays with the caller, same as
the manual example above:
import tinox.core.amqp091;
@Amqp091Consumer("localhost", 5672, "/", "guest", "guest", "tinox-demo-queue")
class DemoConsumer091
{
@OnMessage
fn onMessage(msg: AmqpMessage091) -> Nothing
{
// decode msg.body (List<Int64>) yourself, then e.g. log it
}
}
amqp10
import tinox.core.amqp10;
AMQP-1.0 client (no broker), a standalone module with no
code shared with amqp091 — a completely different type
system (a generic format-code codec instead of fixed field tags) and a
three-level Connection→Session→Link
hierarchy with credit-based flow control (flow/
link-credit) instead of 0-9-1's simple
Connection→Channel model. v1: one session/one
link per purpose, SASL PLAIN only.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Amqp10Connection::connect | fn connect(host: String, port: Int64, user: String, pass: String) -> Amqp10Connection | SASL header + PLAIN negotiation, then AMQP header + open. .conn <= 0 + .errorMessage on any failure. |
Amqp10Connection::close | fn close() -> Nothing | Closes the connection per spec (sends close). |
Amqp10Session::begin | fn begin(connection: Amqp10Connection) -> Amqp10Session | Opens a session on the connection (v1: fixed channel 1, no pool). |
Amqp10Session::end | fn end() -> Nothing | Ends the session (sends end). |
Amqp10Link::attach | fn attach(session: Amqp10Session, name: String, role: Bool, address: String) -> Amqp10Link | Attaches a sender (role=false) or receiver link (role=true) to the given address (RabbitMQ 4.x: /queues/<name>, "AMQP Address v2"). |
Amqp10Link::detach | fn detach() -> Nothing | Detaches the link again (sends detach). |
Amqp10Link::awaitFlow | fn awaitFlow() -> Nothing | Reads a flow frame and updates linkCredit. Called internally by publish() when no credit remains. |
Amqp10Link::grantCredit | fn grantCredit(amount: Int64) -> Nothing | Sends flow with link-credit (receiver-side, needed before nextMessage()). |
Amqp10Link::publish | fn publish(body: List<Int64>, contentType: String) -> Nothing | Publishes a message (settled=true, fire-and-forget). Automatically waits for a new flow if credit is exhausted; splits the body across multiple transfer frames (more=true) once max-frame-size is exceeded. |
Amqp10Link::nextMessage | fn nextMessage() -> Amqp10Message | Blocking pull of the next message (requires grantCredit()). Decodes both data AND amqp-value body sections. Does NOT reassemble multi-frame transfers on receive (v1 gap). |
Amqp10Link::ack | fn ack(deliveryId: Int64) -> Nothing | Acknowledges a message received via nextMessage() (disposition with state=accepted). |
Amqp10Message | { body: List<Int64>, contentType: String, deliveryId: Int64, ok: Bool, errorMessage: String } | A message received via nextMessage(). ok == false on any failure (including no recognized body section), errorMessage describes the cause. |
Example
import tinox.core.amqp10;
let conn = Amqp10Connection::connect("127.0.0.1", 5672, "guest", "guest");
let session = Amqp10Session::begin(conn);
var sender = Amqp10Link::attach(session, "my-sender", false, "/queues/my-queue");
var body: List<Int64> = [];
for i in 0..3 { body.push("abc".charCodeAt(i)); }
sender.publish(body, "text/plain");
sender.detach();
var receiver = Amqp10Link::attach(session, "my-receiver", true, "/queues/my-queue");
receiver.grantCredit(10);
let m = receiver.nextMessage();
if m.ok {
receiver.ack(m.deliveryId);
}
conn.close();
Annotation-driven variant
Like WebSocket, the compiler can alternatively generate the whole
connect/attach/receive loop itself — see
Annotations (@Amqp10Consumer/
@OnMessage). Only applies if the file defines no own
main and contains exactly one @Amqp10Consumer
class (more than one is a compile error, not silently ignored). The
handler receives the whole Amqp10Message (not an already
decoded String) — body decoding stays with the caller, exactly like the
manual example above:
import tinox.core.amqp10;
@Amqp10Consumer("localhost", 5672, "guest", "guest", "/queues/tinox-demo-queue")
class DemoConsumer
{
@OnMessage
fn onMessage(msg: Amqp10Message) -> Nothing
{
// decode msg.body (List<Int64>) yourself, then e.g. log it
}
}
Deliberate v1 gaps: multi-frame reassembly on receive
(sending already splits correctly once max-frame-size is
exceeded; receiving only detects more=true and reports an
error instead of reassembling).
smtp
import tinox.core.smtp;
SMTP client (RFC 5321) for sending email — the common "send a password
reset email / signup confirmation" need. Supports plaintext connect, RFC
3207 STARTTLS (upgrades an already-connected plaintext
session in place, port 587), implicit TLS (port 465), and SASL
AUTH PLAIN (RFC 4954/4616). connect()/
connectTls() read the greeting and send EHLO
automatically; startTls() re-issues EHLO after
the upgrade, as RFC 3207 requires.
v1 sends a plain-text body only — no multipart/HTML/attachments. No DKIM signing (the sending mail server's job when routing through a real relay like SES/SendGrid, not this client library's).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
SmtpClient::connect |
fn connect(host: String, port: Int64) -> SmtpClient |
Plaintext connect, reads the greeting, sends EHLO. Check
conn <= 0/errorMessage for failure. |
SmtpClient::connectTls |
fn connectTls(host: String, port: Int64, verify: Bool) -> SmtpClient |
Implicit-TLS variant of connect (port 465). |
SmtpClient::startTls |
fn startTls() -> Bool |
RFC 3207: upgrades the connection to TLS in place (port 587 flow), then re-sends EHLO. Certificate verification is always on. |
SmtpClient::authPlain |
fn authPlain(user: String, pass: String) -> Bool |
SASL AUTH PLAIN. |
SmtpClient::send |
fn send(from: String, to: List<String>, subject: String, body: String) -> Bool |
Sends a plain-text email (MAIL FROM/RCPT TO/DATA,
with RFC 5321 §4.5.2 dot-stuffing handled automatically). |
SmtpClient::close |
fn close() -> Nothing |
Closes the connection. |
Example
import tinox.core.smtp;
let client: SmtpClient = SmtpClient::connect("smtp.example.org", 587);
if client.conn <= 0 { throw client.errorMessage; }
if !client.startTls() { throw "STARTTLS failed"; }
if !client.authPlain("user", "pass") { throw "auth failed"; }
client.send("me@example.org", ["you@example.org"], "Hi", "Hello!");
client.close();
redis
import tinox.core.redis;
Client for an external cache/session store (RESP2, single-node,
request/response commands) — tinox.core.cache's in-process
Cache/LruCache can't share state across HTTP
worker processes/instances or store sessions outside a single process's
memory. Wire-compatible with both Redis and Valkey.
v1 scope: no connection pooling (tinox.core.pool is the
generic building block for this, not reinvented here), no Redis
Cluster/Sentinel, no pub/sub (SUBSCRIBE/PUBLISH)
— single-node, request/response commands only. No Memcached client in
this pass (different wire protocol, an explicit separately-scoped
follow-up if needed).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
RedisClient::connect |
fn connect(host: String, port: Int64) -> RedisClient |
Dials and sends PING to fail fast on a bad
host/port. Check conn <= 0/errorMessage
for failure. |
RedisClient::get |
fn get(key: String) -> String |
Returns "" both on a real cache miss and on a
protocol error — lastGetFound() distinguishes the two,
since an empty string is itself a valid stored value. |
RedisClient::lastGetFound |
fn lastGetFound() -> Bool |
Whether the most recent get() found a value. Check
immediately after calling get(). |
RedisClient::set |
fn set(key: String, value: String) -> Bool |
Sets a key unconditionally. |
RedisClient::setEx |
fn setEx(key: String, value: String, ttlSeconds: Int64) -> Bool |
Sets a key with an expiration. |
RedisClient::del |
fn del(key: String) -> Bool |
Deletes a key; true if it existed. |
RedisClient::expire |
fn expire(key: String, ttlSeconds: Int64) -> Bool |
Sets a TTL on an existing key. |
RedisClient::incr |
fn incr(key: String) -> Int64 |
Atomically increments a key and returns the new value.
lastIncrOk() distinguishes a real result of 0 from a
protocol/value error. |
RedisClient::lastIncrOk |
fn lastIncrOk() -> Bool |
Whether the most recent incr() succeeded. |
RedisClient::close |
fn close() -> Nothing |
Closes the connection. |
Example
import tinox.core.redis;
let client: RedisClient = RedisClient::connect("localhost", 6379);
if client.conn <= 0 { throw client.errorMessage; }
client.setEx("session:abc", "user-42", 3600);
let value: String = client.get("session:abc");
if !client.lastGetFound() { throw "cache miss"; }
client.close();
hpack
import tinox.core.hpack;
Implementation of HPACK header compression for HTTP/2 according to RFC 7541. The module provides an encoder and decoder that work with a static table (61 entries) and a configurable dynamic table per connection. Supports all four representation types from RFC 7541 §6: indexed headers, literals with/without incremental indexing, and dynamic table size updates.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
HpackHeader::new | fn new(name: String, value: String) -> HpackHeader | Creates a header name/value pair. |
HpackDynTable::new | fn new(maxSize: Int64) -> HpackDynTable | Creates a new dynamic HPACK table with the specified maximum size (in bytes). |
HpackDynTable::add | fn add(header: HpackHeader) -> Nothing | Inserts a header at the front and evicts older entries from the back if necessary. |
HpackDynTable::setMaxSize | fn setMaxSize(newSize: Int64) -> Nothing | Changes the maximum size of the table and evicts entries if necessary. |
Hpack::decode | fn decode(data: List<Int64>, dynTable: HpackDynTable) -> List<HpackHeader> | Decodes a complete HPACK header block into an ordered list of headers. |
Hpack::encode | fn encode(headers: List<HpackHeader>, dynTable: HpackDynTable) -> List<Int64> | Encodes a header list into an HPACK header block. Uses indexed representation for full matches in the static table, otherwise literal with incremental indexing. |
Hpack::decodeInt | fn decodeInt(data: List<Int64>, offset: Int64, prefixBits: Int64) -> HpackIntResult | Decodes an HPACK integer with the specified prefix width (RFC 7541 §5.1). |
Hpack::decodeStr | fn decodeStr(data: List<Int64>, offset: Int64) -> HpackStrResult | Decodes an HPACK string literal (RFC 7541 §5.2). Huffman encoding is passed through without error, but not decoded. |
Hpack::encodeInt | fn encodeInt(value: Int64, prefixBits: Int64, firstByteMask: Int64) -> List<Int64> | Encodes an integer with the given prefix width (RFC 7541 §5.1). |
Hpack::encodeStr | fn encodeStr(s: String) -> List<Int64> | Encodes a string without Huffman encoding (RFC 7541 §5.2). |
Hpack::lookupHeader | fn lookupHeader(dynTable: HpackDynTable, index: Int64) -> HpackHeader | Looks up a header by the combined static+dynamic index (1-based). |
Hpack::findStatic | fn findStatic(name: String, value: String) -> Int64 | Searches for a full or name-based match in the static table; returns -1 if not found. |
Hpack::staticName | fn staticName(index: Int64) -> String | Returns the header name for a static table index (indices 1–61). |
Hpack::staticValue | fn staticValue(index: Int64) -> String | Returns the header value for a static table index (only entries with a value). |
Example
import tinox.core.hpack;
// Dynamic tables for encoder and decoder (per connection)
let encTable: HpackDynTable = HpackDynTable::new(4096);
let decTable: HpackDynTable = HpackDynTable::new(4096);
// Header-Liste kodieren
let headers: List<HpackHeader> = [];
headers.push(HpackHeader::new(":status", "200"));
headers.push(HpackHeader::new("content-type", "application/json"));
headers.push(HpackHeader::new("x-request-id", "abc-123"));
let encoded: List<Int64> = Hpack::encode(headers, encTable);
// ... send encoded over the network ...
// Decode on receiver side
let decoded: List<HpackHeader> = Hpack::decode(encoded, decTable);
let i: Int64 = 0;
while i < decoded.len() {
print(decoded[i].name + ": " + decoded[i].value);
i = i + 1;
}
// Adjust table size to peer specification
encTable.setMaxSize(2048);
http
import tinox.core.http;
Simple HTTP client for direct network requests. The class
Http provides static methods for all common HTTP verbs
(GET, POST, PUT, PATCH, DELETE) and the ability to set and clear global default headers.
Responses are returned as HttpClientResponse, which provides the status code,
body, and individual header values.
For higher-level abstraction, use the rest module.
https:// is supported (issue #131) —
detected automatically from the URL scheme, no separate method
needed. The certificate chain and hostname are always verified
against the system CA stores (no opt-out like connectTls/
listenTls in other modules — this client is
intentionally v1-scoped to real, publicly-trusted endpoints only,
e.g. OAuth2 token endpoints, see the oauth2 module).
When built with TINOX_TLS=0, an https://
call returns the same empty HttpClientResponse
(statusCode() == 0) as any other connection failure.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Http::get | fn get(url: String) -> HttpClientResponse | Executes an HTTP GET request to the specified URL. |
Http::post | fn post(url: String, body: String) -> HttpClientResponse | Executes an HTTP POST request with the specified body. |
Http::put | fn put(url: String, body: String) -> HttpClientResponse | Executes an HTTP PUT request with the specified body. |
Http::delete | fn delete(url: String) -> HttpClientResponse | Executes an HTTP DELETE request. |
Http::patch | fn patch(url: String, body: String) -> HttpClientResponse | Executes an HTTP PATCH request with the specified body. |
Http::setHeader | fn setHeader(name: String, value: String) -> Nothing | Sets a global default header for subsequent requests. |
Http::clearHeaders | fn clearHeaders() -> Nothing | Clears all previously set global headers. |
HttpClientResponse::statusCode | fn statusCode() -> Int64 | Returns the HTTP status code of the response. |
HttpClientResponse::body | fn body() -> String | Returns the response body as a string. |
HttpClientResponse::header | fn header(name: String) -> String | Returns the value of a response header. |
Example
import tinox.core.http;
// Simple GET request
let resp: HttpClientResponse = Http::get("https://api.example.com/status");
print("Status: " + resp.statusCode().toString());
print("Body: " + resp.body());
// POST with JSON body and Authorization header
Http::setHeader("Authorization", "Bearer my-token");
Http::setHeader("Content-Type", "application/json");
let post: HttpClientResponse = Http::post(
"https://api.example.com/users",
"{\"name\": \"Max\", \"email\": \"max@example.com\"}"
);
if post.statusCode() == 201 {
print("User created: " + post.body());
} else {
print("Error: " + post.statusCode().toString());
}
Http::clearHeaders();
// PATCH-Request
let patch: HttpClientResponse = Http::patch(
"https://api.example.com/users/42",
"{\"name\": \"Maxine\"}"
);
print("Content-Type: " + patch.header("Content-Type"));
rest.client
import tinox.core.rest.client;
RESTful HTTP client with convenient abstraction over the low-level
http module. Provides RestClient for requests with
default headers and authentication, and RequestBuilder
for fluent (method-chaining) request construction. Responses are wrapped
in RestResponse, which offers convenient helpers for status checks
and JSON parsing. Additionally, the module contains URL helper functions
and enums for HTTP status codes and MIME types.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
RestClient::new | fn new(baseUrl: String) -> RestClient | Creates a new REST client with the specified base URL (timeout 30 s). |
RestClient::newWithHeaders | fn newWithHeaders(baseUrl: String, defaultHeaders: List<String>) -> RestClient | Creates a client with pre-configured default headers. |
RestClient::setTimeout | fn setTimeout(timeoutMs: Int64) -> Nothing | Sets the request timeout in milliseconds. |
RestClient::addHeader | fn addHeader(name: String, value: String) -> Nothing | Adds a default header for all requests. |
RestClient::clearHeaders | fn clearHeaders() -> Nothing | Clears all default headers. |
RestClient::get | fn get(path: String) -> RestResponse | Executes a GET request to baseUrl + path. |
RestClient::getWithParams | fn getWithParams(path: String, params: Map<String, String>) -> RestResponse | GET request with query parameters (URL-encoded). |
RestClient::post | fn post(path: String, body: String) -> RestResponse | POST request with JSON body (sets Content-Type automatically). |
RestClient::put | fn put(path: String, body: String) -> RestResponse | PUT request with JSON body. |
RestClient::patch | fn patch(path: String, body: String) -> RestResponse | PATCH request with JSON body. |
RestClient::delete | fn delete(path: String) -> RestResponse | DELETE request. |
RestClient::bearerAuth | fn bearerAuth(token: String) -> Nothing | Adds a Bearer token Authorization header. |
RestClient::basicAuth | fn basicAuth(username: String, password: String) -> Nothing | Adds a Basic Auth header (Base64-encoded). |
RestResponse::new | fn new(statusCode: Int64, body: String) -> RestResponse | Creates a REST response instance. |
RestResponse::text | fn text() -> String | Returns the response body as a string. |
RestResponse::json | fn json() -> JsonValue | Parses the body as JSON. |
RestResponse::isOk | fn isOk() -> Bool | Returns true if the status code is in the range 200–299. |
RestResponse::isClientError | fn isClientError() -> Bool | Returns true for status codes 400–499. |
RestResponse::isServerError | fn isServerError() -> Bool | Returns true for status codes 500+. |
RestResponse::status | fn status() -> Int64 | Returns the numeric status code. |
RestResponse::ensureSuccess | fn ensureSuccess() -> RestResponse | Throws an error if the response was not successful (not 2xx). |
Url::build | fn build(baseUrl: String, path: String) -> String | Joins base URL and path into a complete URL. |
Url::buildWithParams | fn buildWithParams(baseUrl: String, path: String, params: Map<String, String>) -> String | Builds a URL with URL-encoded query parameters. |
Url::encode | fn encode(s: String) -> String | Percent-encodes a string for use in URLs. |
RequestBuilder::new | fn new(baseUrl: String) -> RequestBuilder | Creates a fluent request builder. |
RequestBuilder::header | fn header(name: String, value: String) -> RequestBuilder | Adds a header to the request (chaining). |
RequestBuilder::query | fn query(key: String, value: String) -> RequestBuilder | Adds a query parameter (chaining). |
RequestBuilder::jsonBody | fn jsonBody(jsonBody: String) -> RequestBuilder | Sets a JSON body and adds the Content-Type header (chaining). |
RequestBuilder::bearer | fn bearer(token: String) -> RequestBuilder | Adds a Bearer token header (chaining). |
RequestBuilder::get | fn get(path: String) -> RestResponse | Executes the configured GET request. |
RequestBuilder::post | fn post(path: String) -> RestResponse | Executes the configured POST request. |
Example
import tinox.core.rest.client;
// RestClient mit Bearer-Auth
let client: RestClient = RestClient::new("https://api.example.com");
client.bearerAuth("mein-jwt-token");
// GET mit Query-Parametern
let params: Map<String, String> = Map::new();
params["page"] = "1";
params["limit"] = "20";
let list: RestResponse = client.getWithParams("/users", params);
list.ensureSuccess();
print(list.text());
// POST – create new user
let create: RestResponse = client.post("/users", "{\"name\": \"Anna\"}");
if create.isOk() {
let data: JsonValue = create.json();
print("Created: " + data.toString());
}
// Fluenter RequestBuilder
let resp: RestResponse = RequestBuilder::new("https://api.example.com")
.bearer("my-jwt-token")
.query("format", "compact")
.jsonBody("{\"active\": true}")
.post("/users/42/settings");
if resp.isClientError() {
print("Clientfehler: " + resp.status().toString());
}
rest.server
import tinox.core.rest.server;
Annotation-driven REST framework for building HTTP APIs with
declarative routing. Controller classes are declared with annotations such as
@Path, @GET, @POST,
@Produces, @Consumes, @StatusCode,
@Auth, and @OIDCRolesAllowed, then registered
with RestApi (or, for purely annotation-based controllers
with no explicit RestApi wiring, translated by the compiler
directly into route-dispatch code, including an auto-generated
main() if none exists). The framework supports global
middleware, CORS, automatic content-type management, and dependency
injection annotations (@ApplicationComponent,
@Inject, @Startup, @HttpRequestScoped).
@Auth("bearer")/@Auth("basic") now
actually validates the credential (since issue #141) instead of
only checking the Authorization header's scheme prefix
(previously any syntactically matching "Bearer ..."/
"Basic ..." with an arbitrary value was accepted). For
controllers registered via the RestApi class, this was
already possible via RestApi::setAuthValidator; for purely
annotation-based controllers (compiled routes with no
RestApi instance), the compiler now looks for a project-
defined class AuthValidator with
fnc validate(authType: String, credential: String) -> Bool
and calls it if present — without such a class, every
@Auth-protected request is rejected (safe default instead
of silently open, matching RestApi::authValidator's own
default behavior).
@OIDCRolesAllowed(["role1", "role2"])
(issue #142) requires a verified OIDC access token carrying at least one
of the listed realm roles — entirely declarative, with the handler
itself never aware any of this happened. Verification (RS256 signature
via the IdP's JWKS, iss/aud/exp/
nbf, role matching against realm_access.roles)
runs automatically via OidcGuard::checkRoles; the IdP
connection itself (OIDC_ISSUER/OIDC_JWKS_URI/
OIDC_AUDIENCE) comes from environment variables, so the
same compiled binary works against any RS256/JWKS-capable IdP without a
rebuild — see examples/keycloak_oidc_api for a full example
with a real Keycloak via docker-compose.
Known issue (issue #140): the compiler's auto-generated,
epoll-/thread-pool-based server for annotation-driven routes
(tinox_HttpServer_listen in runtime.c — a
different, multi-threaded server from the single-threaded
HttpServer class documented below) can crash inside the
Boehm GC after a handful of requests to any allocation-
heavy route (not specific to @OIDCRolesAllowed —
reproduces with a trivial route with no crypto/JSON/network
involvement). Light, interactive use works reliably; the
HttpServer class itself is unaffected.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
OIDCRolesAllowed | @OIDCRolesAllowed(roles: List<String>) | Method annotation (issue #142): requires a verified OIDC access token carrying at least one of the listed realm roles. Needs OIDC_ISSUER/OIDC_JWKS_URI/OIDC_AUDIENCE as runtime environment variables. |
OidcGuard::checkRoles | fnc checkRoles(ctx: HttpContext, rolesCsv: String) -> Bool | Backs @OIDCRolesAllowed — called by the compiler under a well-known symbol name; not meant for direct use from application code. Sets ctx.response itself on failure (401 missing/invalid token, 403 valid token missing the required role). |
RestApi::new | fn new(port: Int64) -> RestApi | Creates a new REST API application on the specified port. |
RestApi::register | fn register(controller: RestController) -> RestApi | Registers all routes of a controller (chaining). |
RestApi::get | fn get(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApi | Registers a GET handler directly (fluent API). |
RestApi::post | fn post(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApi | Registers a POST handler directly. |
RestApi::put | fn put(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApi | Registers a PUT handler directly. |
RestApi::patch | fn patch(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApi | Registers a PATCH handler directly. |
RestApi::delete | fn delete(path: String, handler: fnc(HttpContext) -> Nothing) -> RestApi | Registers a DELETE handler directly. |
RestApi::use | fn use(mware: fnc(HttpContext) -> Bool) -> RestApi | Adds a global middleware. |
RestApi::enableCors | fn enableCors(origin: String) -> RestApi | Enables CORS for all responses with the specified allowed origin. |
RestApi::defaultJson | fn defaultJson() -> RestApi | Sets application/json as the default content type if not already set. |
RestApi::start | fn start() -> Nothing | Registers all routes and starts the HTTP server (blocking). |
RestApi::stop | fn stop() -> Nothing | Stops the server. |
RestApi::wrapHandler | fn wrapHandler(entry: RouteEntry) -> fnc(HttpContext) -> Nothing | Wraps a handler with annotation metadata: status code, content type, auth check. |
RestController::new | fn new() -> RestController | Creates a new controller (base class for annotated controllers). |
RestController::route | fn route(method: String, path: String, handler: fnc(HttpContext) -> Nothing) -> RouteEntry | Registers a route manually on the controller. |
RouteEntry::new | fn new(method: String, path: String, handler: fnc(HttpContext) -> Nothing) -> RouteEntry | Creates a new route entry. |
RouteEntry::withStatus | fn withStatus(code: Int64) -> RouteEntry | Sets the default status code for the route (chaining). |
RouteEntry::withProduces | fn withProduces(mediaType: MediaType) -> RouteEntry | Sets the response content type of the route (chaining). |
RouteEntry::withConsumes | fn withConsumes(mediaType: MediaType) -> RouteEntry | Sets the expected request content type of the route (chaining). |
RouteEntry::withAuth | fn withAuth(authType: String) -> RouteEntry | Sets the authentication type of the route ("bearer" or "basic") (chaining). |
UrlBuilder::join | fn join(base: String, path: String) -> String | Joins base and route path without double slashes. |
Example
import tinox.core.rest.server;
@Path("/api/users")
class UserController : RestController
{
fn new() -> UserController
{
let ctrl: UserController = UserController { routes: [], basePath: "/api/users" };
ctrl.route("GET", "/", ctx => {
ctx.response.json("[{\"id\": 1, \"name\": \"Alice\"}]");
}).withProduces(MediaType::Json);
ctrl.route("POST", "/", ctx => {
let body: JsonValue = ctx.request.json();
ctx.response.status(201).json("{\"created\": true}");
}).withStatus(201).withConsumes(MediaType::Json).withProduces(MediaType::Json);
ctrl.route("GET", "/:id", ctx => {
let id: String = ctx.request.getParam("id");
ctx.response.json("{\"id\": " + id + "}");
}).withProduces(MediaType::Json).withAuth("bearer");
return ctrl;
}
}
let app: RestApi = RestApi::new(8080);
app.enableCors("*");
app.defaultJson();
app.use(ctx => {
print("Request: " + ctx.request.method + " " + ctx.request.path);
return true;
});
app.register(UserController::new());
app.start();
socket
import tinox.core.socket;
Simple socket abstraction for TCP and UDP network communication.
The Socket class encapsulates native system sockets and provides
methods for connecting, binding, listening, accepting connections,
sending and receiving data, and closing the socket.
TCP sockets are typically used for connection-oriented protocols,
UDP sockets for connectionless communication.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Socket::createTcp | fn createTcp() -> Socket | Creates a new TCP socket. |
Socket::createUdp | fn createUdp() -> Socket | Creates a new UDP socket. |
Socket::connect | fn connect(socket: Socket, host: String, port: Int64) -> Bool | Connects the socket to the specified host and port. Returns true on success. |
Socket::bind | fn bind(socket: Socket, port: Int64) -> Bool | Binds the socket to a local port. |
Socket::listen | fn listen(socket: Socket) -> Bool | Puts the socket into listen mode (TCP server only). |
Socket::accept | fn accept(socket: Socket) -> Socket | Accepts an incoming connection and returns a new socket. |
Socket::send | fn send(socket: Socket, data: String) -> Int64 | Sends data over the socket. Returns the number of bytes sent. |
Socket::receive | fn receive(socket: Socket, size: Int64) -> String | Receives up to size bytes from the socket and returns them as a string. |
Socket::close | fn close(socket: Socket) -> Nothing | Closes the socket and frees system resources. |
Example
import tinox.core.socket;
// === TCP-Server ===
let server: Socket = Socket::createTcp();
let bound: Bool = Socket::bind(server, 9000);
if bound == false {
print("Bind fehlgeschlagen");
}
let listening: Bool = Socket::listen(server);
print("Server listening on port 9000");
// Accept incoming connection
let client: Socket = Socket::accept(server);
// Receive data
let data: String = Socket::receive(client, 1024);
print("Received: " + data);
// Send response
Socket::send(client, "HTTP/1.1 200 OK\r\n\r\nHello!");
Socket::close(client);
Socket::close(server);
// === TCP-Client ===
let conn: Socket = Socket::createTcp();
let ok: Bool = Socket::connect(conn, "api.example.com", 80);
if ok {
Socket::send(conn, "GET / HTTP/1.1\r\nHost: api.beispiel.de\r\n\r\n");
let resp: String = Socket::receive(conn, 4096);
print(resp);
Socket::close(conn);
}
jwt
import tinox.core.jwt;
JSON Web Token (JWT) implementation with HMAC-SHA256 (HS256) and,
since issue #138, RSASSA-PKCS1-v1_5-SHA256 (RS256, against a provider
key fetched via Jwks) signing. The module allows creating
signed JWTs from a payload map, verifying the signature, decoding the
header/payload, and checking the expiry time based on the
exp claim. All three JWS segments are base64url-encoded
(RFC 7515 §3) — before issue #137 the signature segment was
incorrectly raw hex instead of base64url, so no external JWT client
could verify a Tinox-issued token (or vice versa); since then the
module interoperates with standard libraries (verified against PyJWT,
among others).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Jwt::encode | fn encode(payload: Map<String, JsonValue>, secret: String) -> String | Creates an HS256-signed JWT string from the payload map and the secret key. |
Jwt::decode | fn decode(token: String, secret: String) -> Map<String, JsonValue> | Verifies the HS256 signature (header alg must be exactly "HS256") and returns the decoded payload. Throws on invalid format, wrong alg, or wrong signature. |
Jwt::verify | fn verify(token: String, secret: String) -> Bool | Checks whether the HS256 signature is valid without returning the payload. |
Jwt::decodeRs256 | fn decodeRs256(token: String, n: String, e: String) -> Map<String, JsonValue> | RS256 counterpart of decode — n/e are an RSA public key from a JWK (base64url, e.g. via Jwks::fetchRsaKey). Checks header alg == "RS256" (alg-confusion hardening). |
Jwt::verifyRs256 | fn verifyRs256(token: String, n: String, e: String) -> Bool | RS256 counterpart of verify. |
Jwt::extractHeader | fn extractHeader(token: String) -> Map<String, JsonValue> | Decodes the JWT header (for alg/kid) without signature verification — needed to know which key/algorithm to verify with before verification happens (see OidcClient, module oidc). |
Jwt::extractPayload | fn extractPayload(token: String) -> Map<String, JsonValue> | Decodes the payload of a token without signature verification (only for trusted sources). |
Jwt::isExpired | fn isExpired(token: String) -> Bool | Returns true if the exp claim is in the past. Tokens without exp are considered not expired. |
Jwks::fetchRsaKey | fn fetchRsaKey(jwksUri: String, kid: String) -> Map<String, String> | Fetches a JWKS document (RFC 7517 §5) over HTTPS and returns {"n", "e"} of the RSA key matching kid. kid == "" is only unambiguous if the JWKS contains exactly one RSA key — otherwise this throws rather than silently using the first match. |
Example
import tinox.core.jwt;
let secret: String = "my-secret-key";
// Create token
let payload: Map<String, JsonValue> = Map::new();
payload["sub"] = JsonValue::fromString("user-42");
payload["name"] = JsonValue::fromString("Anna Sample");
payload["exp"] = JsonValue::fromInt(Time::now() / 1000 + 3600); // 1 hour
let token: String = Jwt::encode(payload, secret);
print("Token: " + token);
// Verify signature
let valid: Bool = Jwt::verify(token, secret);
print("Valid: " + valid.toString());
// Decode token and read payload
let decoded: Map<String, JsonValue> = Jwt::decode(token, secret);
print("User: " + decoded["name"].asString());
// Check expiry
let expired: Bool = Jwt::isExpired(token);
print("Expired: " + expired.toString());
// Middleware example: validate Bearer token
let checkJwt: fnc(HttpContext) -> Bool = ctx => {
let auth: String = ctx.request.getHeader("Authorization");
if auth.startsWith("Bearer ") == false {
ctx.response.status(401).text("No token");
return false;
}
let tok: String = auth.substring(7, auth.len());
if Jwt::verify(tok, secret) == false || Jwt::isExpired(tok) {
ctx.response.status(401).text("Token invalid or expired");
return false;
}
return true;
};
oauth2
import tinox.core.oauth2;
OAuth2 client for the Authorization Code grant with PKCE (RFC 6749 +
RFC 7636) — the recommended flow for both server and public clients
against third-party providers (Google, GitHub, …).
OAuth2Client encapsulates the full flow: building the
authorize URL (including a secure state for CSRF
protection and a PKCE code_challenge), exchanging the
callback code for a token pair, and refreshing tokens. The
underlying http client gained https://
support alongside this module (strict certificate verification), so
real provider endpoints can be reached directly.
Intentional v1 gaps: only Authorization Code + PKCE
(no client_credentials/implicit/
device_code), no token storage — as with the rest of
the rest/http_server framework, that
remains the caller's responsibility. For "login with X" including a
verified OIDC ID token (signature, iss/aud),
see the oidc module (OidcClient, issue
#138), which builds on this client.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
OAuth2Client::new |
fn new(authorizeUrl: String, tokenUrl: String, clientId: String, clientSecret: String, redirectUri: String) -> OAuth2Client |
Creates a client for a concrete provider (authorize/token endpoint) and this application (client ID/secret, registered redirect URI). |
OAuth2Client::buildAuthorizeUrl |
fn buildAuthorizeUrl(scope: String) -> OAuth2AuthorizeRequest |
Builds the URL the user's browser is redirected to. Generates a cryptographically secure state (CSRF) and a PKCE code_verifier/code_challenge (S256) along the way. The state and codeVerifier from the result MUST be stored session-side. |
OAuth2Client::exchangeCode |
fn exchangeCode(code: String, codeVerifier: String) -> OAuth2TokenResponse |
Exchanges the code delivered by the provider in the callback for an access/refresh token pair (RFC 6749 §4.1.3, form-urlencoded POST). codeVerifier must be the previously stored value from buildAuthorizeUrl. |
OAuth2Client::refresh |
fn refresh(refreshToken: String) -> OAuth2TokenResponse |
Exchanges a refresh token for a fresh token pair (RFC 6749 §6). |
OAuth2AuthorizeRequest |
{ url: String, state: String, codeVerifier: String } |
Result of buildAuthorizeUrl. state/codeVerifier must be kept until the callback. |
OAuth2TokenResponse |
{ accessToken, refreshToken, tokenType, idToken: String, expiresIn: Int64, ok: Bool, errorMessage: String } |
ok == false on any failure (network/TLS, HTTP error status, or an RFC 6749 §5.2 error response from the provider) — errorMessage describes the cause instead of silently passing through an empty/incorrect token. idToken is the token endpoint's id_token field, if present ("" otherwise) — unverified, see the oidc module for verified OIDC claims. |
Example
import tinox.core.oauth2;
class Main
{
fnc main() -> Int32
{
let client: OAuth2Client = OAuth2Client::new(
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"my-client-id",
"my-client-secret",
"https://my-app.example.com/callback"
);
// 1. Redirect the user to the provider's login page
let authReq: OAuth2AuthorizeRequest = client.buildAuthorizeUrl("openid email");
// Store authReq.state and authReq.codeVerifier session-side,
// then redirect the browser to authReq.url.
println("Redirecting to: " + authReq.url);
// 2. In the callback handler: verify state against the stored
// value, then exchange the code for a token pair.
let tokens: OAuth2TokenResponse = client.exchangeCode("code-from-callback", authReq.codeVerifier);
if tokens.ok {
println("Access token: " + tokens.accessToken);
} else {
println("Error: " + tokens.errorMessage);
}
// 3. Later: refresh the token
let refreshed: OAuth2TokenResponse = client.refresh(tokens.refreshToken);
if refreshed.ok {
println("New access token: " + refreshed.accessToken);
}
return 0;
}
}
oidc
import tinox.core.oidc;
"Login with X" (OpenID Connect) on top of OAuth2Client
(module oauth2): OidcClient runs the same
Authorization Code + PKCE flow, but also verifies the provider's
id_token — RS256 signature against the provider key
fetched via Jwks (module jwt), plus
iss/aud/exp/nbf —
instead of trusting it unverified, and returns the verified claims
(sub, email, …) (issue #138).
Intentional v1 gaps: no JWKS caching (every
verification fetches the JWKS document fresh — a caching layer is
separate follow-up work if this becomes a real cost), only
Authorization Code + PKCE (no client_credentials/
implicit/device_code), no token storage —
same stance as oauth2.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
OidcClient::new |
fn new(issuer: String, authorizeUrl: String, tokenUrl: String, jwksUri: String, clientId: String, clientSecret: String, redirectUri: String) -> OidcClient |
Like OAuth2Client::new, plus issuer (expected iss claim) and jwksUri (provider JWKS endpoint). |
OidcClient::buildAuthorizeUrl |
fn buildAuthorizeUrl(scope: String) -> OAuth2AuthorizeRequest |
Like OAuth2Client::buildAuthorizeUrl, but first checks that scope includes "openid" — otherwise the provider won't return an id_token at all, which would otherwise only surface as a confusing error at exchangeCode. Throws immediately if "openid" is missing. |
OidcClient::exchangeCode |
fn exchangeCode(code: String, codeVerifier: String) -> OidcTokenResponse |
Exchanges the code for a token pair (like OAuth2Client::exchangeCode) and verifies the id_token. If id_token is missing from the response, that is an error here — unlike refresh. |
OidcClient::refresh |
fn refresh(refreshToken: String) -> OidcTokenResponse |
Refreshes the token pair. If the provider doesn't return a new id_token (optional per spec for this grant), that is not an error — claims is then empty. |
OidcTokenResponse |
{ accessToken, refreshToken, tokenType, idToken: String, expiresIn: Int64, claims: Map<String, JsonValue>, ok: Bool, errorMessage: String } |
ok == false on any OAuth2 failure (see OAuth2TokenResponse) AND on any ID-token verification failure (wrong alg, invalid signature, mismatched iss/aud, expired) — claims is only populated when ok == true and an id_token was present. |
Example
import tinox.core.oidc;
class Main
{
fnc main() -> Int32
{
let client: OidcClient = OidcClient::new(
"https://accounts.google.com",
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"https://www.googleapis.com/oauth2/v3/certs",
"my-client-id",
"my-client-secret",
"https://my-app.example.com/callback"
);
// 1. Redirect the user to the provider's login page ("openid" is required)
let authReq: OAuth2AuthorizeRequest = client.buildAuthorizeUrl("openid email");
println("Redirecting to: " + authReq.url);
// 2. In the callback handler: verify state, then exchange the code + verify id_token
let tokens: OidcTokenResponse = client.exchangeCode("code-from-callback", authReq.codeVerifier);
if tokens.ok {
println("Logged in as: " + tokens.claims["sub"].getString());
} else {
println("Error: " + tokens.errorMessage);
}
return 0;
}
}
OidcWebApp — browser login "out of the box"
OidcClient above is the low-level building block — the
caller has to store state/codeVerifier
itself and manage a session. OidcWebApp takes care of
exactly that for the classic "visit a page, get bounced to
Keycloak/Google/…, come back logged in" case (Spring Security's
"OAuth2 Login", Quarkus' application-type=web-app):
install(server) registers /login (redirects
to the provider) and /callback (exchanges the code,
verifies the id_token, starts the session) on a
tinox.core.http_server.HttpServer — deliberately NOT on
the annotation-driven auto-server, which can crash under
allocation-heavy routes (issue #140), which this flow (JSON, a JWKS
fetch, AES) is throughout. There is no server-side session store:
both the short-lived "pending login" state (PKCE verifier + CSRF
state, between /login and /callback) and
the session itself (the verified claims) live entirely in httpOnly
cookies, AES-256-GCM sealed (Crypto::aesEncrypt/
aesDecrypt) with a secret you provide — a tampered
cookie or the wrong secret makes aesDecrypt throw, so
the cookie is rejected rather than accepted. This also means
sessions survive a server restart and work across multiple processes
with no shared state.
Class / Method Signature Description
OidcWebApp::new
fn new(client: OidcClient, cookieSecret: String) -> OidcWebApp
Throws if cookieSecret is empty (an empty secret is a well-known, guessable key, not "encryption off").
OidcWebApp::install
fn install(server: HttpServer) -> Nothing
Registers /login and /callback (paths configurable via loginPath/callbackPath) on server. Call once at startup, before server.listen().
OidcWebApp::requireLogin
fn requireLogin(ctx: HttpContext) -> Bool
Session gate for a route: on a missing/tampered/expired session cookie, redirects to loginPath and returns false (caller must return; immediately), otherwise true.
OidcWebApp::currentUser
fn currentUser(ctx: HttpContext) -> Map<String, JsonValue>
The verified claims of the current session — meaningful right after a successful requireLogin in the same request.
OidcWebApp::logout
fn logout(ctx: HttpContext) -> Nothing
Clears the session cookie and redirects to loginPath.
Example
import tinox.core.oidc;
import tinox.core.http_server;
class Main
{
fnc main() -> Int32
{
let client: OidcClient = OidcClient::new(
"https://accounts.google.com",
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"https://www.googleapis.com/oauth2/v3/certs",
"my-client-id",
"my-client-secret",
"https://my-app.example.com/callback"
);
let app: OidcWebApp = OidcWebApp::new(client, "my-cookie-secret");
let server: HttpServer = HttpServer::new(8096);
app.install(server);
server.get("/", ctx => {
if !app.requireLogin(ctx) { return; }
let claims: Map<String, JsonValue> = app.currentUser(ctx);
ctx.response.text("Hello, " + claims["sub"].getString());
});
server.get("/logout", ctx => { app.logout(ctx); });
server.listen();
return 0;
}
}
A complete example, verified against a real Keycloak instance, lives
at examples/keycloak_oidc_api/src/WebLogin.tnx.
json
import tinox.core.json;
The json module provides classes and annotations to parse,
serialize, and type-safely process JSON data. With the @JsonSerializable
annotation, the compiler automatically generates toJson() and fromJson() methods for
arbitrary classes. The Json class serves as a central facade, while JsonValue
represents a parsed JSON node and enables type queries and value access.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Json::parse |
fnc parse(text: String) -> JsonValue |
Parses a JSON string and returns a JsonValue node. |
Json::stringify |
fnc stringify(value: JsonValue) -> String |
Serializes a JsonValue back into a JSON string. |
Json::getField |
fnc getField(obj: JsonValue, key: String) -> JsonValue |
Reads a named field from a JSON object. |
Json::serialize |
fnc serialize<T>(obj: T) -> String |
Calls obj.toJson() and returns the resulting JSON string. Requires @JsonSerializable on the class. |
Json::deserialize |
fnc deserialize<T>(text: String) -> T |
Parses the JSON string and constructs an instance of type T via T::fromJson(). |
Json::intArrayFromJson |
fnc intArrayFromJson(arr: JsonValue) -> List<Int64> |
Converts a JSON array into a Tinox list of Int64 values. |
Json::intArrayToString |
fnc intArrayToString(arr: List<Int64>) -> String |
Serializes an Int64 list as a JSON array string. |
Json::intArrayWrap |
fnc intArrayWrap(key: String, arr: List<Int64>) -> String |
Wraps an Int64 list as a named JSON object, e.g. {"ids":[1,2,3]}. |
JsonValue::getString |
fn getString() -> String |
Returns the value as a String. |
JsonValue::getInt |
fn getInt() -> Int64 |
Returns the value as Int64. |
JsonValue::getFloat |
fn getFloat() -> Float64 |
Returns the value as Float64. |
JsonValue::getBool |
fn getBool() -> Bool |
Returns the value as Bool. |
JsonValue::isNull |
fn isNull() -> Bool |
Checks whether the value is null. |
JsonValue::isString / isInt / isFloat / isBool / isObject / isArray |
fn isX() -> Bool |
Type-checking methods for each JSON type. |
JsonValue::getField |
fn getField(key: String) -> JsonValue |
Reads a field of the JSON object by its name. |
JsonValue::len |
fn len() -> Int64 |
Number of elements, if the value is a JSON array (0 otherwise). |
JsonValue::get |
fn get(index: Int64) -> JsonValue |
Reads an array element by index (hard error on an out-of-range index). |
JsonValue::asList |
fn asList() -> List<JsonValue> |
Materializes a JSON array as a List<JsonValue> (issue #138, e.g. for JWKS' keys array in the jwt module). |
Example
import tinox.core.json;
@JsonSerializable
class Product
{
@JsonField("product_id")
var id: Int64;
var name: String;
var price: Float64;
}
class Main
{
fnc main() -> Int32
{
// Serialize object
let p: Produkt = Produkt { id: 42, name: "Pen", price: 1.99 };
let jsonStr: String = Json::serialize(p);
println(jsonStr);
// Parse JSON and read fields
let raw: String = "{\"title\":\"Test\",\"value\":7}";
let v: JsonValue = Json::parse(raw);
let title: String = v.getField("title").getString();
let value: Int64 = v.getField("value").getInt();
println(title + " = " + value.toString());
// Int array round-trip
let ids: List = [1, 2, 3];
let wrapped: String = Json::intArrayWrap("ids", ids);
println(wrapped);
// Deserialize
let p2: Produkt = Json::deserialize(jsonStr);
println(p2.name);
return 0;
}
}
msgpack
import tinox.core.msgpack;
Compact binary serialization (MessagePack),
a binary counterpart to json's text-based JsonValue
tree — useful for AMQP message payloads, inter-service RPC, or just
storing structured data more compactly than JSON. No schema/codegen
step, same self-describing dynamic-value model as JSON (object/array/
string/int/float/bool/null). MsgpackValue mirrors
JsonValue's API shape (isString/getString,
isInt/getInt, isArray/asArray,
isObject/getField, ...) but — unlike the C-backed
JsonValue — is a plain Tinox value: construct one directly
via a struct literal for encoding, e.g.
MsgpackValue { kind: "string", stringValue: "hi" }.
Encoding always picks the most compact representation that fits
(fixint/fixstr/fixarray/fixmap where possible, otherwise the smallest
width that holds the value). Decoding handles every core MessagePack
type an external encoder could legitimately produce (all int widths
signed/unsigned, both float widths, all string/array/map length-prefix
widths) — verified against Python's independent msgpack
library in both directions, and fuzz-tested (fuzz/msgpack/).
v1 scope: no bin8/16/32 (raw byte strings) or the
ext family (application-specific extension types) — neither has
a JsonValue-model equivalent to represent it as. A decoded map
with a non-string key fails the whole decode (this module's object
model only supports string keys, matching JsonValue). A decoded
uint64 value ≥ 2⁶³ comes out as a negative Int64
(the same 64 raw bits reinterpreted as signed) — Tinox's Int64
has no wider/unsigned counterpart to hold the full range.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Msgpack::encode |
fn encode(value: MsgpackValue) -> List<Int64> |
Encodes a value tree to MessagePack bytes. |
Msgpack::decode |
fn decode(bytes: List<Int64>) -> MsgpackValue |
Decodes MessagePack bytes into a value tree. Never crashes or hangs on truncated/malformed input (matching this stdlib's other self-contained-buffer parsers, it doesn't expose a separate success/failure flag either — a malformed input decodes to whatever partial/null result naturally falls out). |
MsgpackValue |
isNull/isBool/isInt/isFloat/isString/isArray/isObject() -> Bool |
Type checks. |
MsgpackValue |
getBool/getInt/getFloat/getString() -> ... |
Scalar accessors. |
MsgpackValue |
asArray() -> List<MsgpackValue>, asMap() -> Map<String, MsgpackValue> |
Container accessors. |
MsgpackValue::getField |
fn getField(key: String) -> MsgpackValue |
Reads an object field; returns a null-kind value if this isn't an object or the key is absent. |
Example
import tinox.core.msgpack;
var fields: Map<String, MsgpackValue> = Map::new();
fields["name"] = MsgpackValue { kind: "string", stringValue: "Tinox" };
fields["version"] = MsgpackValue { kind: "int", intValue: 2 };
let root: MsgpackValue = MsgpackValue { kind: "object", objectValue: fields };
let bytes: List<Int64> = Msgpack::encode(root);
let decoded: MsgpackValue = Msgpack::decode(bytes);
println(decoded.getField("name").getString());
xml
import tinox.core.xml;
The xml module enables parsing, traversing, and rendering XML documents.
Xml is the central facade class; XmlNode represents a
single node in the XML tree. Attributes, text content, and child nodes are accessible via methods.
XmlParser and XmlRenderer provide the internal
parsing and output logic, while XmlQuery supports XPath-like queries.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Xml::parse |
fn parse(text: String) -> XmlNode |
Parses an XML string and returns the root node. |
Xml::stringify |
fn stringify(node: XmlNode) -> String |
Renders an XmlNode tree back into an XML string. |
Xml::query |
fn query(node: XmlNode, xpath: String) -> List<XmlNode> |
Executes an XPath-like query on the node tree and returns all matches. |
XmlNode::tagName |
fn tagName() -> String |
Returns the tag name of the node (e.g. "person"). |
XmlNode::textContent |
fn textContent() -> String |
Returns the text content of the node. |
XmlNode::attr |
fn attr(name: String) -> String |
Reads the value of a named attribute. |
XmlNode::children |
fn children() -> List<XmlNode> |
Returns all direct child nodes as a list. |
XmlNode::firstChild |
fn firstChild(tag: String) -> XmlNode |
Returns the first child with the specified tag name; throws an error if none is found. |
XmlParser::parse |
fn parse(text: String) -> XmlNode |
Internal method: trims the text and starts parsing a single node. |
XmlRenderer::render |
fn render(node: XmlNode) -> String |
Internal method: recursively generates the XML string for the given node. |
Example
import tinox.core.xml;
class Main
{
fnc main() -> Int32
{
let document: String = "<catalog>" +
"<book id=\"1\"><title>Tinox Basics</title></book>" +
"<book id=\"2\"><title>Advanced Tinox</title></book>" +
"</catalog>";
let root: XmlNode = Xml::parse(document);
println("Root tag: " + root.tagName());
let children: List<XmlNode> = root.children();
var i: Int64 = 0;
while i < children.len()
{
let book: XmlNode = children[i];
let id: String = book.attr("id");
let title: XmlNode = book.firstChild("title");
println("Book " + id + ": " + title.textContent());
i = i + 1;
}
// Render back to XML string
let output: String = Xml::stringify(root);
println(output);
return 0;
}
}
yaml
import tinox.core.yaml;
The yaml module provides support for the YAML format via the classes
Yaml, YamlValue, YamlParser, and YamlRenderer.
YamlValue is a type-discrete value container with a kind field
(e.g. "string", "int", "map", "list").
Output is indented, with each indentation level using two spaces.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Yaml::parse |
fn parse(content: String) -> YamlValue |
Parses a YAML string and returns a YamlValue. |
Yaml::stringify |
fn stringify(value: YamlValue) -> String |
Renders a YamlValue tree as an indented YAML string (starting indentation 0). |
YamlValue::isString |
fn isString() -> Bool |
Checks whether the value is a string. |
YamlValue::isInt |
fn isInt() -> Bool |
Checks whether the value is an integer. |
YamlValue::isFloat |
fn isFloat() -> Bool |
Checks whether the value is a floating-point number. |
YamlValue::isBool |
fn isBool() -> Bool |
Checks whether the value is a boolean. |
YamlValue::isList |
fn isList() -> Bool |
Checks whether the value is a list. |
YamlValue::isMap |
fn isMap() -> Bool |
Checks whether the value is a map (key-value pairs). |
YamlRenderer::render |
fn render(value: YamlValue, indent: Int64) -> String |
Internal method: renders a YamlValue recursively with the specified indentation level. |
YamlRenderer::indent |
fn indent(level: Int64) -> String |
Returns an indentation string with level * 2 spaces. |
Example
import tinox.core.yaml;
class Main
{
fnc main() -> Int32
{
// Build YamlValue tree manually
let name: YamlValue = YamlValue {
kind: "string",
stringValue: "Tinox"
};
let version: YamlValue = YamlValue {
kind: "int",
intValue: 2
};
let m: Map<String, YamlValue> = Map::new();
m["name"] = name;
m["version"] = version;
let root: YamlValue = YamlValue {
kind: "map",
mapValue: m
};
// Output as YAML string
let yamlStr: String = Yaml::stringify(root);
println(yamlStr);
// Type checks
println(name.isString().toString()); // true
println(version.isInt().toString()); // true
println(root.isMap().toString()); // true
// Re-parse (returns empty map as placeholder)
let parsed: YamlValue = Yaml::parse(yamlStr);
println(parsed.isMap().toString());
return 0;
}
}
csv
import tinox.core.csv;
The csv module provides the Csv class for reading and writing
comma-separated values (CSV). The parser correctly handles
fields enclosed in quotation marks and commas within
quotation marks. When serializing, fields with commas or
quotation marks are automatically quoted correctly.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Csv::parse |
fn parse(content: String) -> List<List<String>> |
Parses a complete CSV text and returns it as a two-dimensional list. Empty lines are skipped. |
Csv::parseRow |
fn parseRow(line: String) -> List<String> |
Parses a single CSV line character by character and correctly handles quotation marks. |
Csv::toString |
fn toString(data: List<List<String>>) -> String |
Serializes a two-dimensional list back into a CSV string with line breaks. |
Csv::rowToString |
fn rowToString(row: List<String>) -> String |
Serializes a single row. Fields with commas or " are automatically quoted; contained " are doubled. |
Example
import tinox.core.csv;
class Main
{
fnc main() -> Int32
{
let csvText: String =
"name,age,city\n" +
"Anna,30,Berlin\n" +
"\"Müller, Klaus\",45,\"Frankfurt am Main\"\n" +
"Lea,22,Hamburg\n";
let tabelle: List<List<String>> = Csv::parse(csvText);
// Print header row
let header: List<String> = tabelle[0];
println(header[0] + " | " + header[1] + " | " + header[2]);
// Print data rows
var i: Int64 = 1;
while i < tabelle.len()
{
let row: List<String> = tabelle[i];
println(row[0] + " is " + row[1] + " years old, lives in " + row[2]);
i = i + 1;
}
// Add new row and serialize back
let newRow: List<String> = ["Tom", "28", "Munich"];
tabelle.push(newRow);
let output: String = Csv::toString(tabelle);
println(output);
return 0;
}
}
toml
import tinox.core.toml;
The toml module enables reading TOML configuration files.
TomlParser reads sections ([section]), key-value pairs,
and automatically recognizes the types String, Bool, Float, and Int.
TomlValue is a polymorphic value container with type-checking and access methods.
Rendering (writing) of TOML is prepared as an extension point.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Toml::parse |
fn parse(content: String) -> TomlValue |
Parses a TOML string and returns a TomlValue table. |
Toml::stringify |
fn stringify(value: TomlValue) -> String |
Serializes a TomlValue tree into a TOML string (extension point). |
TomlValue::isString |
fn isString() -> Bool |
Checks whether the value is a string. |
TomlValue::isInt |
fn isInt() -> Bool |
Checks whether the value is an integer. |
TomlValue::isFloat |
fn isFloat() -> Bool |
Checks whether the value is a floating-point number. |
TomlValue::isBool |
fn isBool() -> Bool |
Checks whether the value is a boolean. |
TomlValue::isTable |
fn isTable() -> Bool |
Checks whether the value is a TOML table (section). |
TomlValue::isArray |
fn isArray() -> Bool |
Checks whether the value is a TOML array. |
TomlValue::asString |
fn asString() -> String |
Returns the value as a String. |
TomlValue::asInt |
fn asInt() -> Int64 |
Returns the value as Int64. |
TomlValue::asFloat |
fn asFloat() -> Float64 |
Returns the value as Float64. |
TomlValue::asBool |
fn asBool() -> Bool |
Returns the value as Bool. |
TomlValue::asTable |
fn asTable() -> Map<String, TomlValue> |
Returns the contained table as a map. |
TomlValue::asArray |
fn asArray() -> List<TomlValue> |
Returns the contained array as a list. |
TomlParser::parseValue |
fn parseValue(value: String) -> TomlValue |
Automatically detects the type of a raw TOML value (String, Bool, Float, Int). |
Example
import tinox.core.toml;
class Main
{
fnc main() -> Int32
{
let konfigText: String =
"debug = true\n" +
"port = 8080\n" +
"host = \"localhost\"\n" +
"\n" +
"[datenbank]\n" +
"url = \"postgres://localhost/mydb\"\n" +
"pool = 5\n";
let config: TomlValue = Toml::parse(konfigText);
// Wurzel-Tabelle auslesen
let tabelle: Map<String, TomlValue> = config.asTable();
let port: Int64 = tabelle["port"].asInt();
let host: String = tabelle["host"].asString();
let debug: Bool = tabelle["debug"].asBool();
println("Host: " + host + ", Port: " + port.toString());
println("Debug: " + debug.toString());
// Untersektion
let db: Map<String, TomlValue> = tabelle["datenbank"].asTable();
let dbUrl: String = db["url"].asString();
let pool: Int64 = db["pool"].asInt();
println("DB: " + dbUrl + " (Pool: " + pool.toString() + ")");
return 0;
}
}
ini
import tinox.core.ini;
The ini module processes classic INI configuration files.
Comment lines starting with ; or #, as well as empty lines,
are ignored during parsing. Sections are recognized as [Name].
IniConfig holds all sections as a nested map and provides type-safe
read and write methods with default values.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Ini::parse |
fn parse(content: String) -> IniConfig |
Parses an INI string and returns an IniConfig object. |
Ini::stringify |
fn stringify(config: IniConfig) -> String |
Serializes an IniConfig object back into an INI-formatted string. |
IniConfig::new |
fn new() -> IniConfig |
Creates a new, empty IniConfig instance. |
IniConfig::getString |
fn getString(config: IniConfig, section: String, key: String, default: String) -> String |
Reads a string value from the specified section. Returns default if the section or key is missing. |
IniConfig::getInt |
fn getInt(config: IniConfig, section: String, key: String, default: Int64) -> Int64 |
Reads an integer value. Returns default if the key does not exist. |
IniConfig::setString |
fn setString(config: IniConfig, section: String, key: String, value: String) -> Nothing |
Sets a string value in the section. Automatically creates the section if it does not yet exist. |
Example
import tinox.core.ini;
class Main
{
fnc main() -> Int32
{
let iniText: String =
"; Server configuration\n" +
"[server]\n" +
"host=127.0.0.1\n" +
"port=3000\n" +
"\n" +
"[logging]\n" +
"level=info\n" +
"# Maximum file size in MB\n" +
"maxsize=10\n";
let config: IniConfig = Ini::parse(iniText);
// Read values
let host: String = IniConfig::getString(config, "server", "host", "localhost");
let port: Int64 = IniConfig::getInt(config, "server", "port", 80);
let level: String = IniConfig::getString(config, "logging", "level", "warn");
println("Server: " + host + ":" + port.toString());
println("Log level: " + level);
// Add value
IniConfig::setString(config, "server", "timeout", "30");
// Serialize back
let output: String = Ini::stringify(config);
println(output);
return 0;
}
}
base64
import tinox.core.base64;
The base64 module provides functions for Base64 encoding and decoding
of strings and byte lists. Base64::encode and Base64::decode
delegate to native runtime functions (base64Encode / base64Decode),
while encodeBytes encodes a list of integer bytes character by character.
The format is suitable for transmitting binary data in text-based protocols.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Base64::encode |
fn encode(data: String) -> String |
Encodes a string as a Base64 string via the native runtime function. |
Base64::decode |
fn decode(encoded: String) -> String |
Decodes a Base64 string back into the original string. |
Base64::encodeBytes |
fn encodeBytes(data: List<Int64>) -> String |
Encodes a list of byte values (Int64) character by character as a Base64 string. |
Base64::encodeUrlBytes |
fn encodeUrlBytes(data: List<Int64>) -> String |
Like encodeBytes, but with the URL-/filename-safe alphabet (-/_ instead of +//) and without = padding (RFC 4648 §5) — e.g. for PKCE code_verifier/code_challenge in the oauth2 module (issue #131). |
Base64::decodeUrlBytes |
fn decodeUrlBytes(encoded: String) -> List<Int64> |
Decodes base64url (RFC 4648 §5, no padding expected) as a raw byte list — e.g. for JWT segments (RFC 7515 §3) and JWKS n/e fields (RFC 7517 §9.3) in the jwt module (issue #138). |
Example
import tinox.core.base64;
class Main
{
fnc main() -> Int32
{
// Simple string round-trip
let original: String = "Hello Tinox!";
let encoded: String = Base64::encode(original);
println("Encoded: " + encoded);
let decoded: String = Base64::decode(encoded);
println("Decoded: " + decoded);
// Encode byte list
let bytes: List<Int64> = [72, 101, 108, 108, 111]; // "Hello"
let byteEncoded: String = Base64::encodeBytes(bytes);
println("Bytes encoded: " + byteEncoded);
// Use case: HTTP Basic Auth header
let credentials: String = "user:secret";
let authHeader: String = "Basic " + Base64::encode(credentials);
println(authHeader);
// Check that encode/decode is correctly reversible
let test: String = "Special chars: äöü 123";
let roundTrip: String = Base64::decode(Base64::encode(test));
println(roundTrip);
return 0;
}
}
hex
import tinox.core.hex;
The hex module provides the Hex class that encodes and decodes strings
to hexadecimal representations. Each byte is represented as two hex digits
(lowercase). Additionally, Hex::dump provides a
space-separated byte dump representation as known from debuggers.
The internal helper methods nibbleToChar and charToNibble
handle the conversion between nibble and character.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Hex::encode |
fn encode(data: String) -> String |
Encodes each character value of the string as two lowercase hexadecimal digits. |
Hex::decode |
fn decode(hex: String) -> String |
Converts a hexadecimal string pairwise back into the original string. |
Hex::dump |
fn dump(data: String) -> String |
Returns a space-separated hex dump representation of the string (e.g. "48 65 6c 6c 6f"). |
Hex::nibbleToChar |
fn nibbleToChar(n: Int64) -> String |
Converts a nibble (0–15) into the corresponding hex character ('0'–'9', 'a'–'f'). |
Hex::charToNibble |
fn charToNibble(c: String) -> Int64 |
Converts a single hex character (0–9, a–f, A–F) into its numeric value (0–15). |
Hex::decodeBytes |
fn decodeBytes(hex: String) -> List<Int64> |
Like decode, but returns the raw bytes as a List<Int64> instead of a Tinox String — important when an embedded 0-byte in the digest would otherwise be silently truncated by the string route (e.g. fromCharCode), such as when converting a Crypto::sha256 hex digest back into raw bytes (see the oauth2 module, issue #131). |
Example
import tinox.core.hex;
class Main
{
fnc main() -> Int32
{
let text: String = "Hello";
// Encode string as hex
let hexEncoded: String = Hex::encode(text);
println("Hex: " + hexEncoded); // 48656c6c6f
// Hex dump (space-separated)
let dump: String = Hex::dump(text);
println("Dump: " + dump); // 48 65 6c 6c 6f
// Decode back
let original: String = Hex::decode(hexEncoded);
println("Original: " + original); // Hello
// Individual nibble conversions
let c: String = Hex::nibbleToChar(10); // "a"
let n: Int64 = Hex::charToNibble("f"); // 15
println("10 -> " + c + ", f -> " + n.toString());
// Use case: check hash output
let hashBytes: String = "\xde\xad\xbe\xef";
println("Hash hex: " + Hex::encode(hashBytes));
return 0;
}
}
encoding
import tinox.core.encoding;
The encoding module provides basic character encoding helper methods:
conversion between characters and numeric code points, UTF-8 byte list processing,
and HTML entity encoding and decoding. The Encoding class is built as
a pure facade — all methods operate statelessly on their inputs.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Encoding::toCharCode |
fn toCharCode(s: String) -> Int64 |
Returns the numeric code point of the first character of the string. Empty string yields 0. |
Encoding::fromCharCode |
fn fromCharCode(code: Int64) -> String |
Converts a numeric code point into the corresponding character as a string. |
Encoding::toUtf8 |
fn toUtf8(s: String) -> List<Int64> |
Returns the code points of all characters of the string as an Int64 list. |
Encoding::fromUtf8 |
fn fromUtf8(codes: List<Int64>) -> String |
Builds a string from a list of code points. |
Encoding::htmlEncode |
fn htmlEncode(s: String) -> String |
Replaces the HTML special characters &, <, >, ", and ' with their corresponding HTML entities. |
Encoding::htmlDecode |
fn htmlDecode(s: String) -> String |
Converts HTML entities (&, <, >, ", ') back into their original characters. |
Example
import tinox.core.encoding;
class Main
{
fnc main() -> Int32
{
// Codepoint-Konvertierungen
let code: Int64 = Encoding::toCharCode("A");
println("'A' hat Codepoint: " + code.toString()); // 65
let zeichen: String = Encoding::fromCharCode(9731); // ☃
println("Codepoint 9731: " + zeichen);
// UTF-8 Byte-Liste
let text: String = "Hi!";
let bytes: List<Int64> = Encoding::toUtf8(text);
println("Bytes: " + bytes[0].toString() + " " +
bytes[1].toString() + " " +
bytes[2].toString()); // 72 105 33
let restored: String = Encoding::fromUtf8(bytes);
println("Restored: " + restored); // Hi!
// HTML encoding for safe browser output
let userInput: String = "<script>alert(\"XSS\")</script>";
let safe: String = Encoding::htmlEncode(userInput);
println(safe);
// HTML decoding
let entities: String = "<b>Bold</b>";
let original: String = Encoding::htmlDecode(entities);
println(original);
return 0;
}
}
array
import tinox.core.array;
The array module provides the helper class Arrays
that encapsulates frequently needed operations on List<Int64>:
searching, statistics (min, max, sum, average), transformations (reverse,
filter), sorting via bubble sort, and generating ranges and
prefilled lists.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Arrays::containsInt |
fn containsInt(arr: List<Int64>, value: Int64) -> Bool |
Returns true if value is contained in arr. |
Arrays::indexOfInt |
fn indexOfInt(arr: List<Int64>, value: Int64) -> Int64 |
Returns the index of the first occurrence of value, or -1 if not found. |
Arrays::findMin |
fn findMin(arr: List<Int64>) -> Int64 |
Returns the smallest element in arr. The array must not be empty. |
Arrays::findMax |
fn findMax(arr: List<Int64>) -> Int64 |
Returns the largest element in arr. The array must not be empty. |
Arrays::sum |
fn sum(arr: List<Int64>) -> Int64 |
Calculates the sum of all elements. |
Arrays::average |
fn average(arr: List<Int64>) -> Int64 |
Calculates the integer average (sum / length, truncated). Array must not be empty. |
Arrays::reverseInt |
fn reverseInt(arr: List<Int64>) -> List<Int64> |
Returns a new list with the elements in reversed order. |
Arrays::filterPositive |
fn filterPositive(arr: List<Int64>) -> List<Int64> |
Returns a new list containing only elements with value > 0. |
Arrays::bubbleSort |
fn bubbleSort(arr: List<Int64>) -> List<Int64> |
Sorts the list in ascending order using bubble sort and returns the result. |
Arrays::isSorted |
fn isSorted(arr: List<Int64>) -> Bool |
Checks whether the elements are in non-descending order. |
Arrays::range |
fn range(start: Int64, end: Int64) -> List<Int64> |
Generates a list with values start, start+1, …, end-1 (exclusive end). |
Arrays::fill |
fn fill(value: Int64, count: Int64) -> List<Int64> |
Creates a list with count copies of value. |
Example
import tinox.core.array;
class Main
{
fnc main() -> Int32
{
let zahlen: List<Int64> = [-3, 7, 2, -1, 5, 0, 9];
let pos: List<Int64> = Arrays::filterPositive(zahlen);
// pos = [7, 2, 5, 9]
let sortiert: List<Int64> = Arrays::bubbleSort(pos);
// sortiert = [2, 5, 7, 9]
let min: Int64 = Arrays::findMin(sortiert); // 2
let max: Int64 = Arrays::findMax(sortiert); // 9
let s: Int64 = Arrays::sum(sortiert); // 23
let avg: Int64 = Arrays::average(sortiert); // 5
let idx: Int64 = Arrays::indexOfInt(sortiert, 7); // 2
let hat5: Bool = Arrays::containsInt(sortiert, 5); // true
let rev: List<Int64> = Arrays::reverseInt(sortiert);
// rev = [9, 7, 5, 2]
let r: List<Int64> = Arrays::range(1, 6); // [1,2,3,4,5]
let f: List<Int64> = Arrays::fill(0, 4); // [0,0,0,0]
let ok: Bool = Arrays::isSorted(sortiert); // true
return 0;
}
}
collections
import tinox.core.collections;
The collections module provides three generic data structures:
a LIFO Stack<T>, a FIFO Queue<T>,
and an ordered value pair Pair<T, U>.
The factory class Collections contains shorthand methods for creating them.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Collections::newStack |
fn newStack<T>() -> Stack<T> |
Creates an empty stack. |
Collections::newQueue |
fn newQueue<T>() -> Queue<T> |
Creates an empty queue. |
Collections::newPair |
fn newPair<T, U>(first: T, second: U) -> Pair<T, U> |
Creates a new pair from two values. |
Stack::new |
fn new() -> Stack<T> |
Creates an empty stack directly via the class. |
Stack::push |
fn push(value: T) -> Nothing |
Pushes a value onto the top of the stack. |
Stack::pop |
fn pop() -> T |
Removes and returns the top element. Throws an error on empty stack. |
Stack::peek |
fn peek() -> T |
Returns the top element without removing it. Throws an error on empty stack. |
Stack::isEmpty |
fn isEmpty() -> Bool |
Returns true if the stack is empty. |
Stack::size |
fn size() -> Int64 |
Returns the number of elements in the stack. |
Queue::new |
fn new() -> Queue<T> |
Creates an empty queue directly via the class. |
Queue::enqueue |
fn enqueue(value: T) -> Nothing |
Inserts a value at the end of the queue. |
Queue::dequeue |
fn dequeue() -> T |
Removes and returns the front element. Throws an error on empty queue. |
Queue::peek |
fn peek() -> T |
Returns the front element without removing it. Throws an error on empty queue. |
Queue::isEmpty |
fn isEmpty() -> Bool |
Returns true if the queue is empty. |
Queue::size |
fn size() -> Int64 |
Returns the number of elements in the queue. |
Pair::new |
fn new(first: T, second: U) -> Pair<T, U> |
Creates a pair with two values. |
Pair::getFirst |
fn getFirst() -> T |
Returns the first value of the pair. |
Pair::getSecond |
fn getSecond() -> U |
Returns the second value of the pair. |
Example
import tinox.core.collections;
class Main
{
fnc main() -> Int32
{
// Stack-Verwendung
let s: Stack<Int64> = Stack<Int64>::new();
s.push(10);
s.push(20);
s.push(30);
let top: Int64 = s.peek(); // 30
let popped: Int64 = s.pop(); // 30
let groesse: Int64 = s.size(); // 2
// Queue-Verwendung
let q: Queue<String> = Collections::newQueue<String>();
q.enqueue("alpha");
q.enqueue("beta");
q.enqueue("gamma");
let erstes: String = q.dequeue(); // "alpha"
let leer: Bool = q.isEmpty(); // false
// Pair-Verwendung
let p: Pair<String, Int64> = Collections::newPair<String, Int64>("Alter", 42);
let schluessel: String = p.getFirst(); // "Alter"
let alter: Int64 = p.getSecond(); // 42
return 0;
}
}
heap
import tinox.core.heap;
The heap module implements a generic binary heap
(Heap<T>) with a customizable comparator, as well as
the pre-built specializations MinHeap<T> (smallest
element first) and MaxHeap<T> (largest element first).
The internal helper methods siftUp, siftDown, and
buildHeap are also available.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Heap::new |
fn new() -> Heap<T> |
Creates an empty min-heap with the default comparator a < b. |
Heap::newWithCompare |
fn newWithCompare(comparator: fnc(T, T) -> Bool) -> Heap<T> |
Creates a heap with a custom comparator function. |
Heap::push |
fn push(heap: Heap<T>, item: T) -> Nothing |
Inserts an element into the heap and restores the heap property. |
Heap::pop |
fn pop(heap: Heap<T>) -> T |
Removes and returns the root element (minimum/maximum). Throws an error on empty heap. |
Heap::peek |
fn peek(heap: Heap<T>) -> T |
Returns the root element without removing it. Throws an error on empty heap. |
Heap::siftUp |
fn siftUp(heap: Heap<T>, startIndex: Int64) -> Nothing |
Restores the heap property upward (used internally after push). |
Heap::siftDown |
fn siftDown(heap: Heap<T>, startIndex: Int64) -> Nothing |
Restores the heap property downward (used internally after pop). |
Heap::isEmpty |
fn isEmpty(heap: Heap<T>) -> Bool |
Returns true if the heap contains no elements. |
Heap::size |
fn size(heap: Heap<T>) -> Int64 |
Returns the number of elements in the heap. |
Heap::buildHeap |
fn buildHeap(heap: Heap<T>, items: List<T>) -> Nothing |
Builds the heap in O(n) from an existing list. |
MinHeap::new |
fn new() -> MinHeap<T> |
Creates an empty min-heap (smallest element at the root). |
MinHeap::push |
fn push(h: MinHeap<T>, item: T) -> Nothing |
Inserts an element into the min-heap. |
MinHeap::pop |
fn pop(h: MinHeap<T>) -> T |
Removes and returns the smallest element. |
MinHeap::peek |
fn peek(h: MinHeap<T>) -> T |
Returns the smallest element without removing it. |
MaxHeap::new |
fn new() -> MaxHeap<T> |
Creates an empty max-heap (largest element at the root). |
MaxHeap::push |
fn push(h: MaxHeap<T>, item: T) -> Nothing |
Inserts an element into the max-heap. |
MaxHeap::pop |
fn pop(h: MaxHeap<T>) -> T |
Removes and returns the largest element. |
MaxHeap::peek |
fn peek(h: MaxHeap<T>) -> T |
Returns the largest element without removing it. |
Example
import tinox.core.heap;
class Main
{
fnc main() -> Int32
{
// Min-Heap: kleinstes Element zuerst
let min: MinHeap<Int64> = MinHeap<Int64>::new();
MinHeap::push(min, 5);
MinHeap::push(min, 1);
MinHeap::push(min, 3);
let smallest: Int64 = MinHeap::peek(min); // 1
let removed: Int64 = MinHeap::pop(min); // 1
let nextOne: Int64 = MinHeap::pop(min); // 3
// Max-heap: largest element first
let max: MaxHeap<Int64> = MaxHeap<Int64>::new();
MaxHeap::push(max, 5);
MaxHeap::push(max, 1);
MaxHeap::push(max, 8);
let largest: Int64 = MaxHeap::pop(max); // 8
// Generic heap with custom comparator (min-heap for Float64)
let cmp: fnc(Float64, Float64) -> Bool = fnc(a: Float64, b: Float64) -> Bool { return a < b; };
let h: Heap<Float64> = Heap<Float64>::newWithCompare(cmp);
Heap::push(h, 3.14);
Heap::push(h, 1.41);
let leer: Bool = Heap::isEmpty(h); // false
let anz: Int64 = Heap::size(h); // 2
// buildHeap: build heap from list
let h2: Heap<Int64> = Heap<Int64>::new();
Heap::buildHeap(h2, [9, 4, 7, 2, 6]);
let root: Int64 = Heap::peek(h2); // 2
return 0;
}
}
queue
import tinox.core.queue;
The queue module provides two specialized queue types.
PriorityQueue<T> orders elements by an integer priority value (lower number = higher priority).
CircularBuffer<T> is a ring-buffer-like buffer with
fixed capacity that accepts no further elements when full.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
PriorityQueue::new |
fn new() -> PriorityQueue<T> |
Creates an empty priority queue. |
PriorityQueue::enqueue |
fn enqueue(q: PriorityQueue<T>, item: T, priority: Int64) -> Nothing |
Inserts item with the specified priority value (lower value = higher priority). |
PriorityQueue::dequeue |
fn dequeue(q: PriorityQueue<T>) -> T |
Removes and returns the element with the highest priority. Throws an error on empty queue. |
PriorityQueue::peek |
fn peek(q: PriorityQueue<T>) -> T |
Returns the element with the highest priority without removing it. |
PriorityQueue::sort |
fn sort(q: PriorityQueue<T>) -> Nothing |
Sorts the internal list by priority (called automatically after each enqueue). |
PriorityQueue::isEmpty |
fn isEmpty(q: PriorityQueue<T>) -> Bool |
Returns true if the queue is empty. |
PriorityQueue::size |
fn size(q: PriorityQueue<T>) -> Int64 |
Returns the number of elements. |
CircularBuffer::new |
fn new(size: Int64) -> CircularBuffer<T> |
Creates a circular buffer with the specified maximum capacity. |
CircularBuffer::push |
fn push(buf: CircularBuffer<T>, item: T) -> Bool |
Inserts an element. Returns false if the buffer is full. |
CircularBuffer::pop |
fn pop(buf: CircularBuffer<T>) -> T |
Removes and returns the oldest element. Throws an error on empty buffer. |
CircularBuffer::isFull |
fn isFull(buf: CircularBuffer<T>) -> Bool |
Returns true if the buffer is full. |
CircularBuffer::isEmpty |
fn isEmpty(buf: CircularBuffer<T>) -> Bool |
Returns true if the buffer is empty. |
Example
import tinox.core.queue;
class Main
{
fnc main() -> Int32
{
// Priority queue
let pq: PriorityQueue<String> = PriorityQueue<String>::new();
PriorityQueue::enqueue(pq, "Low", 10);
PriorityQueue::enqueue(pq, "Critical", 1);
PriorityQueue::enqueue(pq, "Medium", 5);
let first: String = PriorityQueue::dequeue(pq); // "Critical"
let second: String = PriorityQueue::dequeue(pq); // "Medium"
let size: Int64 = PriorityQueue::size(pq); // 1
// Ring buffer with capacity 3
let buf: CircularBuffer<Int64> = CircularBuffer<Int64>::new(3);
let ok1: Bool = CircularBuffer::push(buf, 100); // true
let ok2: Bool = CircularBuffer::push(buf, 200); // true
let ok3: Bool = CircularBuffer::push(buf, 300); // true
let ok4: Bool = CircularBuffer::push(buf, 400); // false – full
let voll: Bool = CircularBuffer::isFull(buf); // true
let wert: Int64 = CircularBuffer::pop(buf); // 100
let leer: Bool = CircularBuffer::isEmpty(buf); // false
return 0;
}
}
set
import tinox.core.set;
The set module provides the generic class Set<T>
that implements an ordered set without duplicates. In addition to
basic operations (add, remove, contains check), the class supports
classic set operations: union, intersection, difference, and subset check.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Set::new |
fn new() -> Set<T> |
Creates an empty set. |
Set::add |
fn add(set: Set<T>, item: T) -> Nothing |
Adds item to the set if it is not already contained. |
Set::remove |
fn remove(set: Set<T>, item: T) -> Bool |
Removes item from the set. Returns true if the element was present. |
Set::contains |
fn contains(set: Set<T>, item: T) -> Bool |
Returns true if item is contained in the set. |
Set::size |
fn size(set: Set<T>) -> Int64 |
Returns the number of elements in the set. |
Set::isEmpty |
fn isEmpty(set: Set<T>) -> Bool |
Returns true if the set is empty. |
Set::union |
fn union(a: Set<T>, b: Set<T>) -> Set<T> |
Returns the union of a and b (all elements from both). |
Set::intersection |
fn intersection(a: Set<T>, b: Set<T>) -> Set<T> |
Returns the intersection (only elements present in both sets). |
Set::difference |
fn difference(a: Set<T>, b: Set<T>) -> Set<T> |
Returns the difference set (elements in a that are not in b). |
Set::isSubsetOf |
fn isSubsetOf(a: Set<T>, b: Set<T>) -> Bool |
Returns true if all elements of a are also contained in b. |
Example
import tinox.core.set;
class Main
{
fnc main() -> Int32
{
let a: Set<Int64> = Set<Int64>::new();
Set::add(a, 1);
Set::add(a, 2);
Set::add(a, 3);
Set::add(a, 2); // Duplikat – wird ignoriert
let b: Set<Int64> = Set<Int64>::new();
Set::add(b, 2);
Set::add(b, 3);
Set::add(b, 4);
let u: Set<Int64> = Set::union(a, b); // {1,2,3,4}
let s: Set<Int64> = Set::intersection(a, b); // {2,3}
let d: Set<Int64> = Set::difference(a, b); // {1}
let enthalten: Bool = Set::contains(a, 3); // true
let groesse: Int64 = Set::size(a); // 3
let c: Set<Int64> = Set<Int64>::new();
Set::add(c, 2);
Set::add(c, 3);
let teilmenge: Bool = Set::isSubsetOf(c, a); // true
let entfernt: Bool = Set::remove(a, 1); // true
let leer: Bool = Set::isEmpty(a); // false
return 0;
}
}
sort
import tinox.core.sort;
The sort module provides the Sort class with three
classic sorting algorithms for List<Int64>:
Quicksort (average O(n log n), in-place via partition),
Merge Sort (stable, splits and merges recursively), and
Insertion Sort (efficient for small or nearly sorted lists).
All methods return the sorted list.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Sort::quickSort |
fn quickSort(arr: List<Int64>) -> List<Int64> |
Sorts the list in ascending order using quicksort. Delegates to the internal helper. |
Sort::quickSortHelper |
fn quickSortHelper(arr: List<Int64>, low: Int64, high: Int64) -> List<Int64> |
Recursive quicksort on the sub-range [low, high]. |
Sort::partition |
fn partition(arr: List<Int64>, low: Int64, high: Int64) -> Int64 |
Lomuto partitioning: selects arr[high] as pivot and returns the pivot index. |
Sort::mergeSort |
fn mergeSort(arr: List<Int64>) -> List<Int64> |
Sorts the list in ascending order using merge sort (stable algorithm). |
Sort::mergeSortHelper |
fn mergeSortHelper(arr: List<Int64>) -> List<Int64> |
Recursive merge sort helper: splits the list in two halves and merges them. |
Sort::mergeInto |
fn mergeInto(result: List<Int64>, left: List<Int64>, right: List<Int64>) -> List<Int64> |
Merges two sorted sublists into the target list result. |
Sort::insertionSort |
fn insertionSort(arr: List<Int64>) -> List<Int64> |
Sorts the list in ascending order using insertion sort. Especially efficient for small or nearly sorted lists. |
Example
import tinox.core.sort;
class Main
{
fnc main() -> Int32
{
let daten: List<Int64> = [64, 25, 12, 22, 11];
// Quicksort
let qs: List<Int64> = Sort::quickSort(daten);
// qs = [11, 12, 22, 25, 64]
// Merge-Sort
let ms: List<Int64> = Sort::mergeSort(daten);
// ms = [11, 12, 22, 25, 64]
// Insertion sort (good for small lists)
let small: List<Int64> = [5, 3, 1, 4, 2];
let is: List<Int64> = Sort::insertionSort(small);
// is = [1, 2, 3, 4, 5]
// Manual partition for custom algorithms
let arr: List<Int64> = [3, 6, 8, 10, 1, 2, 1];
let pivotIdx: Int64 = Sort::partition(arr, 0, arr.len() - 1);
return 0;
}
}
iter
import tinox.core.iter;
The iter module provides functional iteration tools.
The Iter class offers methods for generating number and
repetition sequences (range, rangeStep,
repeat, cycle), as well as transformation and
aggregation methods (map, filter, reduce,
enumerate, zip). The Iterator class
encapsulates the state of a running iterator with hasNext /
next.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Iter::range |
fn range(start: Int64, end: Int64) -> Iterator |
Creates an iterator over [start, end) with step size 1. |
Iter::rangeStep |
fn rangeStep(start: Int64, end: Int64, step: Int64) -> Iterator |
Creates an iterator over [start, end) with a custom step size. |
Iter::repeat |
fn repeat<T>(value: T, count: Int64) -> List<T> |
Returns a list with count copies of value. |
Iter::cycle |
fn cycle<T>(list: List<T>) -> Iterator |
Creates an infinitely cyclic iterator over the given list. |
Iter::enumerate |
fn enumerate<T>(list: List<T>) -> List<Pair<Int64, T>> |
Returns a list of (index, value) pairs. |
Iter::zip |
fn zip<T, U>(a: List<T>, b: List<U>) -> List<Pair<T, U>> |
Joins two lists element-wise into pairs. The length corresponds to the shorter list. |
Iter::map |
fn map<T, U>(list: List<T>, transform: fnc(T) -> U) -> List<U> |
Applies a transformation function to each element and returns the new list. |
Iter::filter |
fn filter<T>(list: List<T>, predicate: fnc(T) -> Bool) -> List<T> |
Returns a new list containing only elements for which the predicate returns true. |
Iter::reduce |
fn reduce<T, U>(list: List<T>, initial: U, combiner: fnc(U, T) -> U) -> U |
Folds the list with a combiner function into a single value. |
Iterator::hasNext |
fn hasNext() -> Bool |
Returns true if the iterator can still deliver more elements (always true for cyclic iterators). |
Iterator::next |
fn next() -> Int64 |
Returns the next element and advances the pointer. |
Example
import tinox.core.iter;
class Main
{
fnc main() -> Int32
{
// Number range with step
let it: Iterator = Iter::rangeStep(0, 10, 2);
while it.hasNext()
{
let n: Int64 = it.next(); // 0, 2, 4, 6, 8
}
// map: square numbers
let nums: List<Int64> = [1, 2, 3, 4, 5];
let squares: List<Int64> = Iter::map<Int64, Int64>(nums, fnc(x: Int64) -> Int64 { return x * x; });
// [1, 4, 9, 16, 25]
// filter: only even numbers
let even: List<Int64> = Iter::filter<Int64>(nums, fnc(x: Int64) -> Bool { return x % 2 == 0; });
// [2, 4]
// reduce: sum
let sum: Int64 = Iter::reduce<Int64, Int64>(nums, 0, fnc(acc: Int64, x: Int64) -> Int64 { return acc + x; });
// 15
// enumerate
let pairs: List<Pair<Int64, Int64>> = Iter::enumerate<Int64>(nums);
// [(0,1),(1,2),(2,3),(3,4),(4,5)]
// zip
let letters: List<String> = ["a", "b", "c"];
let combined = Iter::zip<Int64, String>(nums, letters);
// [(1,"a"),(2,"b"),(3,"c")]
// repeat
let zeros: List<Int64> = Iter::repeat<Int64>(0, 5);
// [0, 0, 0, 0, 0]
return 0;
}
}
graph
import tinox.core.graph;
The graph module implements a generic directed graph
Graph<V>, where nodes are identified by String identifiers
and edges carry a Float64 weight.
Included are breadth-first search (BFS), depth-first search (DFS), and the
Dijkstra algorithm for computing shortest paths. The helper class
GraphEdge describes a weighted edge.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Graph::new |
fn new() -> Graph<V> |
Creates an empty directed graph. |
Graph::addNode |
fn addNode(graph: Graph<V>, id: String, value: V) -> Nothing |
Adds a node with the identifier id and value value. Initializes the adjacency list if not already present. |
Graph::addEdge |
fn addEdge(graph: Graph<V>, from: String, to: String, weight: Float64) -> Nothing |
Adds a directed edge from from to to with weight weight. |
Graph::neighbors |
fn neighbors(graph: Graph<V>, id: String) -> List<GraphEdge> |
Returns all outgoing edges of node id. |
Graph::bfs |
fn bfs(graph: Graph<V>, start: String, visitor: fnc(String) -> Nothing) -> Nothing |
Performs a breadth-first search from start and calls visitor for each visited node. |
Graph::dfs |
fn dfs(graph: Graph<V>, start: String, visitor: fnc(String) -> Nothing) -> Nothing |
Performs a depth-first search from start and calls visitor for each visited node. |
Graph::dfsHelper |
fn dfsHelper(graph: Graph<V>, current: String, visited: Map<String, Bool>, visitor: fnc(String) -> Nothing) -> Nothing |
Internal recursive helper for depth-first search. |
Graph::dijkstra |
fn dijkstra(graph: Graph<V>, start: String, end: String) -> Float64 |
Computes the shortest path between start and end. Returns the distance, or -1.0 if no path exists. |
Example
import tinox.core.graph;
class Main
{
fnc main() -> Int32
{
let g: Graph<String> = Graph<String>::new();
Graph::addNode(g, "A", "City A");
Graph::addNode(g, "B", "City B");
Graph::addNode(g, "C", "City C");
Graph::addNode(g, "D", "City D");
Graph::addEdge(g, "A", "B", 4.0);
Graph::addEdge(g, "A", "C", 2.0);
Graph::addEdge(g, "C", "B", 1.0);
Graph::addEdge(g, "B", "D", 5.0);
Graph::addEdge(g, "C", "D", 8.0);
// Breadth-first search from A
Graph::bfs(g, "A", fnc(id: String) -> Nothing {
// Visit order: A, B, C, D
});
// Depth-first search from A
Graph::dfs(g, "A", fnc(id: String) -> Nothing {
// Visit order (depth-first): A, B, D, C
});
// Dijkstra: shortest path from A to D
let distance: Float64 = Graph::dijkstra(g, "A", "D");
// A->C (2.0) + C->B (1.0) + B->D (5.0) = 8.0
// Get neighbors of C
let nb: List<GraphEdge> = Graph::neighbors(g, "C");
// nb[0].to = "B", nb[0].weight = 1.0
return 0;
}
}
trie
import tinox.core.trie;
The trie module implements a prefix tree (trie) for
efficient storage and search of strings. In addition to exact
word search and prefix check, the trie provides an
autocomplete function that lists all words stored in the tree
with a given prefix. The internal node structure
TrieNode manages child nodes as Map<String, TrieNode>.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Trie::new |
fn new() -> Trie |
Creates an empty trie with an empty root node. |
Trie::insert |
fn insert(trie: Trie, word: String) -> Nothing |
Inserts the word word character by character into the trie and marks the end. |
Trie::search |
fn search(trie: Trie, word: String) -> Bool |
Returns true if word is fully contained in the trie. |
Trie::startsWith |
fn startsWith(trie: Trie, prefix: String) -> Bool |
Returns true if at least one stored word starts with prefix. |
Trie::autocomplete |
fn autocomplete(trie: Trie, prefix: String) -> List<String> |
Returns all words stored in the trie that start with prefix. |
Trie::collectWords |
fn collectWords(node: TrieNode, prefix: String) -> List<String> |
Recursive helper: collects all complete words below node and appends them to prefix. |
TrieNode::new |
fn new() -> TrieNode |
Creates a new node with empty child map and isEnd = false. |
Example
import tinox.core.trie;
class Main
{
fnc main() -> Int32
{
let t: Trie = Trie::new();
// Insert words
Trie::insert(t, "apfel");
Trie::insert(t, "apfelbaum");
Trie::insert(t, "aprikose");
Trie::insert(t, "banane");
Trie::insert(t, "birne");
// Exact search
let found: Bool = Trie::search(t, "apfel"); // true
let missing: Bool = Trie::search(t, "apfelkuchen"); // false
// Prefix check
let hatAp: Bool = Trie::startsWith(t, "ap"); // true
let hatXy: Bool = Trie::startsWith(t, "xy"); // false
// Autocomplete
let suggestions: List<String> = Trie::autocomplete(t, "ap");
// ["apfel", "apfelbaum", "aprikose"] (order depends on map keys)
let bSuggestions: List<String> = Trie::autocomplete(t, "b");
// ["banane", "birne"]
let noSuggestions: List<String> = Trie::autocomplete(t, "z");
// []
return 0;
}
}
math
import tinox.core.math;
The math module provides basic mathematical operations for integer values. It includes mathematical constants (π, e, τ), comparison and absolute value operations, power and factorial calculation, and a simple pseudo-random number generator based on a linear congruential generator (LCG).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Math::pi |
fn pi() -> Float64 |
Returns the mathematical constant π (3.14159…). |
Math::e |
fn e() -> Float64 |
Returns Euler's number e (2.71828…). |
Math::tau |
fn tau() -> Float64 |
Returns τ = 2 · π (6.28318…). |
Math::abs |
fn abs(x: Int64) -> Int64 |
Returns the absolute value of x (always ≥ 0). |
Math::min |
fn min(a: Int64, b: Int64) -> Int64 |
Returns the smaller of the two values. |
Math::max |
fn max(a: Int64, b: Int64) -> Int64 |
Returns the larger of the two values. |
Math::square |
fn square(x: Int64) -> Int64 |
Returns x² (x · x). |
Math::pow |
fn pow(base: Int64, exp: Int64) -> Int64 |
Calculates base to the power of exp (exp must be ≥ 0). |
Math::factorial |
fn factorial(n: Int64) -> Int64 |
Calculates n! (factorial). Returns 1 for n ≤ 1. |
Math::randomSeed |
fn randomSeed(seed: Int64) -> Int64 |
LCG-based pseudo-random generator. The same seed always produces the same value. |
Example
import tinox.core.math;
class Main
{
fnc main() -> Int32
{
// Konstanten ausgeben
let pi: Float64 = Math::pi();
let e: Float64 = Math::e();
println("Pi = " + pi.toString());
println("e = " + e.toString());
// Grundoperationen
let a: Int64 = -7;
let b: Int64 = 3;
println("abs(-7) = " + Math::abs(a).toString());
println("min(-7, 3) = " + Math::min(a, b).toString());
println("max(-7, 3) = " + Math::max(a, b).toString());
println("square(5) = " + Math::square(5).toString());
// Power and factorial
println("2^10 = " + Math::pow(2, 10).toString());
println("7! = " + Math::factorial(7).toString());
// Pseudo-random
let rnd: Int64 = Math::randomSeed(42);
println("randomSeed(42) = " + rnd.toString());
return 0;
}
}
mathf
import tinox.core.mathf;
The mathf module extends basic math with floating-point operations.
It provides square root, power, absolute value, rounding (floor/ceil/round),
clamp, linear interpolation, and trigonometric and logarithmic functions.
All inputs and outputs are based on Float64.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Mathf::pi |
fn pi() -> Float64 |
Returns π. |
Mathf::e |
fn e() -> Float64 |
Returns Euler's number e. |
Mathf::sqrt |
fn sqrt(x: Float64) -> Float64 |
Calculates the square root using the Heron method. Throws an error for negative input. |
Mathf::pow |
fn pow(base: Float64, exp: Float64) -> Float64 |
Calculates base to the power of exp (integer exponent). |
Mathf::abs |
fn abs(x: Float64) -> Float64 |
Returns the absolute value of a floating-point number. |
Mathf::floor |
fn floor(x: Float64) -> Int64 |
Rounds to the nearest integer toward –∞ (floor). |
Mathf::ceil |
fn ceil(x: Float64) -> Int64 |
Rounds to the nearest integer toward +∞ (ceiling). |
Mathf::round |
fn round(x: Float64) -> Int64 |
Commercial rounding: rounds up from 0.5. |
Mathf::min |
fn min(a: Float64, b: Float64) -> Float64 |
Returns the smaller of two float values. |
Mathf::max |
fn max(a: Float64, b: Float64) -> Float64 |
Returns the larger of two float values. |
Mathf::clamp |
fn clamp(value: Float64, min: Float64, max: Float64) -> Float64 |
Clamps value to the interval [min, max]. |
Mathf::lerp |
fn lerp(a: Float64, b: Float64, t: Float64) -> Float64 |
Linear interpolation between a and b with factor t ∈ [0, 1]. |
Mathf::sin |
fn sin(x: Float64) -> Float64 |
Sine (radians), delegates to sinf. |
Mathf::cos |
fn cos(x: Float64) -> Float64 |
Cosine (radians), delegates to cosf. |
Mathf::tan |
fn tan(x: Float64) -> Float64 |
Tangent (radians), delegates to tanf. |
Mathf::log |
fn log(x: Float64) -> Float64 |
Natural logarithm (base e), delegates to logf. |
Mathf::log10 |
fn log10(x: Float64) -> Float64 |
Decimal logarithm (base 10), delegates to log10f. |
Example
import tinox.core.mathf;
class Main
{
fnc main() -> Int32
{
// Wurzel und Rundung
let r: Float64 = Mathf::sqrt(2.0);
println("sqrt(2) = " + r.toString());
println("floor(2.9) = " + Mathf::floor(2.9).toString());
println("ceil(2.1) = " + Mathf::ceil(2.1).toString());
println("round(2.5) = " + Mathf::round(2.5).toString());
// Clamp und Lerp
let clamped: Float64 = Mathf::clamp(150.0, 0.0, 100.0);
println("clamp(150, 0, 100) = " + clamped.toString());
let mid: Float64 = Mathf::lerp(0.0, 10.0, 0.5);
println("lerp(0, 10, 0.5) = " + mid.toString());
// Trigonometrie
let angle: Float64 = Mathf::pi() / 4.0;
println("sin(pi/4) = " + Mathf::sin(angle).toString());
println("cos(pi/4) = " + Mathf::cos(angle).toString());
// Logarithmen
println("log(e) = " + Mathf::log(Mathf::e()).toString());
println("log10(100) = " + Mathf::log10(100.0).toString());
return 0;
}
}
mathx
import tinox.core.mathx;
The mathx module provides extended number-theoretic and combinatorial functions: GCD, LCM, primality test, next prime, Fibonacci sequence, binomial coefficient, permutations, modular exponentiation, and digit sum and digit count.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Mathx::gcd |
fn gcd(a: Int64, b: Int64) -> Int64 |
Calculates the greatest common divisor (Euclidean algorithm). |
Mathx::lcm |
fn lcm(a: Int64, b: Int64) -> Int64 |
Calculates the least common multiple via |a · b| / gcd(a, b). |
Mathx::isPrime |
fn isPrime(n: Int64) -> Bool |
Returns true if n is a prime number. |
Mathx::nextPrime |
fn nextPrime(n: Int64) -> Int64 |
Returns the smallest prime ≥ n. |
Mathx::fibonacci |
fn fibonacci(n: Int64) -> Int64 |
Calculates the nth Fibonacci number iteratively (F(0)=0, F(1)=1). |
Mathx::combinations |
fn combinations(n: Int64, k: Int64) -> Int64 |
Calculates the binomial coefficient C(n, k) = n! / (k! · (n−k)!). |
Mathx::permutations |
fn permutations(n: Int64, k: Int64) -> Int64 |
Calculates the number of k-permutations of n elements: n! / (n−k)!. |
Mathx::factorial |
fn factorial(n: Int64) -> Int64 |
Internal helper function for factorial, also directly usable. |
Mathx::modPow |
fn modPow(base: Int64, exp: Int64, mod: Int64) -> Int64 |
Fast modular exponentiation via repeated squaring: (base^exp) % mod. |
Mathx::digitSum |
fn digitSum(n: Int64) -> Int64 |
Calculates the digit sum (sum of all decimal digits) of n. |
Mathx::digitCount |
fn digitCount(n: Int64) -> Int64 |
Counts the number of decimal digits of n (special case: 0 yields 1). |
Example
import tinox.core.mathx;
class Main
{
fnc main() -> Int32
{
// ggT und kgV
println("gcd(48, 18) = " + Mathx::gcd(48, 18).toString());
println("lcm(4, 6) = " + Mathx::lcm(4, 6).toString());
// Primzahlen
println("isPrime(17) = " + Mathx::isPrime(17).toString());
println("isPrime(18) = " + Mathx::isPrime(18).toString());
println("nextPrime(20) = " + Mathx::nextPrime(20).toString());
// Fibonacci
println("fibonacci(10) = " + Mathx::fibonacci(10).toString());
// Kombinatorik
println("C(10, 3) = " + Mathx::combinations(10, 3).toString());
println("P(5, 2) = " + Mathx::permutations(5, 2).toString());
// Modulare Potenz und Ziffernanalyse
println("modPow(2, 10, 1000) = " + Mathx::modPow(2, 10, 1000).toString());
println("digitSum(12345) = " + Mathx::digitSum(12345).toString());
println("digitCount(99999) = " + Mathx::digitCount(99999).toString());
return 0;
}
}
complex
import tinox.core.complex;
The complex module provides complex numbers as immutable value types.
It supports creation from Cartesian and polar coordinates, all four
basic arithmetic operations, magnitude, phase, conjugation, reciprocal, and the complex functions
exp, log, and pow. Output is as a readable string in the form a±bi.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Complex::new |
fn new(real: Float64, imag: Float64) -> Complex |
Creates a complex number from real and imaginary parts. |
Complex::fromPolar |
fn fromPolar(r: Float64, theta: Float64) -> Complex |
Creates a complex number from polar coordinates (magnitude r, angle theta in radians). |
Complex::add |
fn add(a: Complex, b: Complex) -> Complex |
Addition of two complex numbers. |
Complex::subtract |
fn subtract(a: Complex, b: Complex) -> Complex |
Subtraction of two complex numbers. |
Complex::multiply |
fn multiply(a: Complex, b: Complex) -> Complex |
Multiplication of two complex numbers using the formula (ac−bd) + (ad+bc)i. |
Complex::divide |
fn divide(a: Complex, b: Complex) -> Complex |
Division a / b via conjugation of the denominator. |
Complex::magnitude |
fn magnitude(c: Complex) -> Float64 |
Calculates the magnitude |c| = sqrt(re² + im²). |
Complex::phase |
fn phase(c: Complex) -> Float64 |
Calculates the phase angle (argument) via atan2(im, re). |
Complex::conjugate |
fn conjugate(c: Complex) -> Complex |
Returns the complex conjugate (sign of imaginary part reversed). |
Complex::reciprocal |
fn reciprocal(c: Complex) -> Complex |
Calculates the reciprocal 1 / c. |
Complex::toString |
fn toString(c: Complex) -> String |
Formats the complex number as a readable string, e.g. 3.0+4.0i. |
Complex::exp |
fn exp(c: Complex) -> Complex |
Calculates e^c = e^re · (cos(im) + i · sin(im)). |
Complex::log |
fn log(c: Complex) -> Complex |
Principal value of the natural logarithm: ln(|c|) + i · arg(c). |
Complex::pow |
fn pow(c: Complex, n: Float64) -> Complex |
Calculates c^n in polar form: |c|^n · e^(i·n·arg(c)). |
Example
import tinox.core.complex;
class Main
{
fnc main() -> Int32
{
// Erzeugung
let a: Complex = Complex::new(3.0, 4.0);
let b: Complex = Complex::new(1.0, -2.0);
// Grundrechenarten
println("a + b = " + Complex::toString(Complex::add(a, b)));
println("a - b = " + Complex::toString(Complex::subtract(a, b)));
println("a * b = " + Complex::toString(Complex::multiply(a, b)));
println("a / b = " + Complex::toString(Complex::divide(a, b)));
// Betrag und Phase
println("|a| = " + Complex::magnitude(a).toString());
println("arg(a) = " + Complex::phase(a).toString());
// Conjugation and reciprocal
println("conj(a) = " + Complex::toString(Complex::conjugate(a)));
println("reciprocal(b)= " + Complex::toString(Complex::reciprocal(b)));
// Polar-Erzeugung und Potenz
let c: Complex = Complex::fromPolar(1.0, Mathf::pi() / 2.0);
println("fromPolar(1, pi/2) = " + Complex::toString(c));
println("a^2 = " + Complex::toString(Complex::pow(a, 2.0)));
return 0;
}
}
decimal
import tinox.core.decimal;
The decimal module implements fixed-point decimal numbers with arbitrary precision based on strings. It enables lossless addition, subtraction, and multiplication without floating-point rounding errors. Division is available with selectable precision (decimal places). Useful for financial and accounting applications.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Decimal::new |
fn new(value: String) -> Decimal |
Creates a decimal number from a string literal. |
Decimal::fromInt |
fn fromInt(value: Int64) -> Decimal |
Creates a decimal number from an integer value (scale = 0). |
Decimal::fromFloat |
fn fromFloat(value: Float64) -> Decimal |
Creates a decimal number from a Float64 value with scale = 10. |
Decimal::add |
fn add(a: Decimal, b: Decimal) -> Decimal |
Adds two decimal numbers with automatic scale alignment. |
Decimal::subtract |
fn subtract(a: Decimal, b: Decimal) -> Decimal |
Subtracts b from a with automatic scale alignment. |
Decimal::multiply |
fn multiply(a: Decimal, b: Decimal) -> Decimal |
Multiplies two decimal numbers; the total scale is the sum of the individual scales. |
Decimal::divide |
fn divide(a: Decimal, b: Decimal, precision: Int64) -> Decimal |
Divides a by b with the specified decimal place precision. |
Decimal::scaleValue |
fn scaleValue(value: String, digits: Int64) -> String |
Internal helper method: appends digits zeros to a digit sequence. |
Decimal::addStrings |
fn addStrings(a: String, b: String) -> String |
Adds two integers represented as strings. |
Decimal::subtractStrings |
fn subtractStrings(a: String, b: String) -> String |
Subtracts two integers represented as strings. |
Decimal::multiplyStrings |
fn multiplyStrings(a: String, b: String) -> String |
Multiplies two integers represented as strings. |
Decimal::toString |
fn toString(d: Decimal) -> String |
Converts a decimal number into its readable string representation with decimal point. |
Example
import tinox.core.decimal;
class Main
{
fnc main() -> Int32
{
// Create from different sources
let price: Decimal = Decimal::new("1999");
let tax: Decimal = Decimal::fromInt(319);
let rate: Decimal = Decimal::fromFloat(0.035);
// Basic arithmetic
let total: Decimal = Decimal::add(price, tax);
println("Price + Tax = " + Decimal::toString(total));
let diff: Decimal = Decimal::subtract(price, tax);
println("Difference = " + Decimal::toString(diff));
let qty: Decimal = Decimal::fromInt(3);
let product: Decimal = Decimal::multiply(price, qty);
println("3 x Price = " + Decimal::toString(product));
// Division with precision
let result: Decimal = Decimal::divide(price, qty, 4);
println("Price / 3 (4 digits) = " + Decimal::toString(result));
return 0;
}
}
random
import tinox.core.random;
The random module provides a convenient interface for generating random values. It provides methods for random integers, floating-point numbers, and booleans, and allows random selection of an element, shuffling, and drawing a random sample from a list. Additionally, a random alphanumeric string can be generated.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Random::nextInt |
fn nextInt(max: Int64) -> Int64 |
Returns a random integer in the range [0, max). |
Random::nextFloat |
fn nextFloat() -> Float64 |
Returns a random floating-point number in the range [0.0, 1.0). |
Random::nextBool |
fn nextBool() -> Bool |
Randomly returns true or false (50% probability). |
Random::pickOne<T> |
fn pickOne<T>(list: List<T>) -> T |
Randomly selects an element from the provided list. |
Random::shuffle<T> |
fn shuffle<T>(list: List<T>) -> List<T> |
Shuffles the list using the Fisher-Yates algorithm and returns the shuffled copy. |
Random::sample<T> |
fn sample<T>(list: List<T>, count: Int64) -> List<T> |
Draws a random sample of count elements without replacement. |
Random::generateString |
fn generateString(length: Int64) -> String |
Generates a random alphanumeric string of the specified length. |
Example
import tinox.core.random;
class Main
{
fnc main() -> Int32
{
// Simple random values
println("nextInt(100) = " + Random::nextInt(100).toString());
println("nextFloat() = " + Random::nextFloat().toString());
println("nextBool() = " + Random::nextBool().toString());
// Pick from a list
let colors: List = ["red", "green", "blue", "yellow"];
println("pickOne = " + Random::pickOne(colors));
// Shuffle
let nums: List = [1, 2, 3, 4, 5, 6, 7, 8];
let shuffled: List = Random::shuffle(nums);
println("shuffle([1..8]) has " + shuffled.len().toString() + " elements");
// Sample
let sample: List = Random::sample(colors, 2);
println("sample(colors, 2) = " + sample.join(", "));
// Random string
let token: String = Random::generateString(16);
println("Token = " + token);
return 0;
}
}
string
import tinox.core.string;
The string module provides the Strings class that bundles
frequently needed string operations as static methods: length query,
uppercase/lowercase, search, extraction, transformation, splitting, joining,
and conversion functions from numbers to strings.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Strings::length |
fn length(s: String) -> Int64 |
Returns the number of characters in the string. |
Strings::isEmpty |
fn isEmpty(s: String) -> Bool |
Returns true if the string is empty (length = 0). |
Strings::toUpperCase |
fn toUpperCase(s: String) -> String |
Converts all characters to uppercase. |
Strings::toLowerCase |
fn toLowerCase(s: String) -> String |
Converts all characters to lowercase. |
Strings::trim |
fn trim(s: String) -> String |
Removes leading and trailing whitespace. |
Strings::contains |
fn contains(haystack: String, needle: String) -> Bool |
Returns true if needle occurs in haystack. |
Strings::startsWith |
fn startsWith(s: String, prefix: String) -> Bool |
Checks whether the string starts with the specified prefix. |
Strings::endsWith |
fn endsWith(s: String, suffix: String) -> Bool |
Checks whether the string ends with the specified suffix. |
Strings::substring |
fn substring(s: String, start: Int64, end: Int64) -> String |
Extracts the substring from index start (incl.) to end (excl.). |
Strings::repeat |
fn repeat(s: String, count: Int64) -> String |
Repeats the string count times and returns the concatenated string. |
Strings::replace |
fn replace(s: String, old: String, repl: String) -> String |
Replaces all occurrences of old with repl. |
Strings::reverse |
fn reverse(s: String) -> String |
Reverses the character order of the string. |
Strings::split |
fn split(s: String, delimiter: String) -> List<String> |
Splits the string at the specified delimiter and returns a list. |
Strings::join |
fn join(parts: List<String>, separator: String) -> String |
Joins a list of strings with the specified delimiter. |
Strings::intToString |
fn intToString(n: Int64) -> String |
Converts an integer into its string representation. |
Strings::floatToString |
fn floatToString(f: Float64) -> String |
Converts a floating-point number into its string representation. |
Example
import tinox.core.string;
class Main
{
fnc main() -> Int32
{
let text: String = " Hello, Tinox-World! ";
// Basic operations
println("Length (trimmed): " + Strings::length(Strings::trim(text)).toString());
println("Uppercase: " + Strings::toUpperCase(Strings::trim(text)));
println("Lowercase: " + Strings::toLowerCase(Strings::trim(text)));
// Search and check
println("Contains 'Tinox': " + Strings::contains(text, "Tinox").toString());
println("Starts with ' H': " + Strings::startsWith(text, " H").toString());
println("Ends with '! ': " + Strings::endsWith(text, "! ").toString());
// Transformation
println("Reverse: " + Strings::reverse("Tinox"));
println("Repeat: " + Strings::repeat("ab", 4));
println("Replace: " + Strings::replace(text, "Tinox", "World"));
// Split and join
let parts: List = Strings::split("a,b,c,d", ",");
println("Parts: " + Strings::join(parts, " | "));
// Number conversion
println(Strings::intToString(42) + " / " + Strings::floatToString(3.14));
return 0;
}
}
fmt
import tinox.core.fmt;
The fmt module provides printf-like formatting for
strings. The Fmt class supports the placeholders %s (string),
%d (integer), %f (float), %x (hexadecimal),
and %b (binary), as well as %% for a literal percent sign.
Output can be produced both as a string and printed directly to the console.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Fmt::sprintf |
fn sprintf(format: String, args: List<String>) -> String |
Formats a string using the format pattern and argument list.
Supported placeholders: %s, %d, %f,
%x (hex), %b (binary), %% (literal %).
|
Fmt::printf |
fn printf(format: String, args: List<String>) -> Nothing |
Prints the formatted string without a trailing newline to the console. |
Fmt::printlnf |
fn printlnf(format: String, args: List<String>) -> Nothing |
Prints the formatted string with a trailing newline to the console. |
Example
import tinox.core.fmt;
class Main
{
fnc main() -> Int32
{
// Simple string interpolation
let name: String = Fmt::sprintf("Hello, %s!", ["Tinox"]);
println(name);
// Mixed types in one line
Fmt::printlnf("User: %s | Age: %d | Balance: %f EUR",
["Alice", "30", "123.45"]);
// Hexadecimal and binary output
Fmt::printlnf("255 in hex: %x", ["255"]);
Fmt::printlnf("255 in bin: %b", ["255"]);
// Literal percent sign
Fmt::printlnf("Discount: 20%%", []);
// Reuse sprintf return value
let line: String = Fmt::sprintf("[%s] Error %d", ["WARN", "404"]);
println(line);
return 0;
}
}
format
import tinox.core.format;
The format module provides helper functions for displaying and aligning values. It enables right-aligned and left-aligned padding of strings to a minimum width, conversion of integers to binary and hexadecimal representations, and formatting of an integer with fixed field width.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Format::padLeft |
fn padLeft(s: String, width: Int64, char: String) -> String |
Pads the string from the left with char until the total width width is reached (right-aligned). |
Format::padRight |
fn padRight(s: String, width: Int64, char: String) -> String |
Pads the string from the right with char until the total width width is reached (left-aligned). |
Format::intToBinary |
fn intToBinary(n: Int64) -> String |
Converts a non-negative integer to its binary representation (e.g. 10 → "1010"). |
Format::intToHex |
fn intToHex(n: Int64) -> String |
Converts a non-negative integer to its hexadecimal representation (lowercase). |
Format::formatInt |
fn formatInt(n: Int64, width: Int64) -> String |
Returns n right-aligned in a field of width width (space as fill character). |
Example
import tinox.core.format;
class Main
{
fnc main() -> Int32
{
// Padding
println(Format::padLeft("42", 6, " ")); // " 42"
println(Format::padLeft("42", 6, "0")); // "000042"
println(Format::padRight("OK", 8, ".")); // "OK......"
// Number bases
println("255 binary: " + Format::intToBinary(255)); // "11111111"
println("255 hex: " + Format::intToHex(255)); // "ff"
println("4096 hex: " + Format::intToHex(4096)); // "1000"
// Tabular output
var i: Int64 = 1;
while i <= 5
{
println(Format::formatInt(i, 4) + " | " + Format::formatInt(i * i, 6));
i = i + 1;
}
return 0;
}
}
regex
import tinox.core.regex;
The regex module provides regular expressions for Tinox. The Regex
class offers methods for checking matches, finding the first or all occurrences, globally
replacing, and splitting text by a pattern. Execution delegates to native runtime functions
(regexIsMatch, regexFindFirst, etc.).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Regex::isMatch |
fn isMatch(pattern: String, text: String) -> Bool |
Returns true if text matches the regular expression pattern. |
Regex::findFirst |
fn findFirst(pattern: String, text: String) -> String |
Returns the first match of the pattern in the text; empty string if no match. |
Regex::findAll |
fn findAll(pattern: String, text: String) -> List<String> |
Returns all non-overlapping matches of the pattern as a list. |
Regex::replaceAll |
fn replaceAll(pattern: String, text: String, replacement: String) -> String |
Replaces all matches of the pattern in the text with replacement. |
Regex::split |
fn split(pattern: String, text: String) -> List<String> |
Splits text at all positions matching the pattern and returns the parts as a list. |
Example
import tinox.core.regex;
class Main
{
fnc main() -> Int32
{
let text: String = "Order no: 1042, Qty: 3, Price: 29.99 EUR";
// Match check
let hasNumber: Bool = Regex::isMatch("[0-9]+", text);
println("Contains numbers: " + hasNumber.toString());
// Find first match
let firstNumber: String = Regex::findFirst("[0-9]+", text);
println("First number: " + firstNumber);
// Find all matches
let allNumbers: List = Regex::findAll("[0-9]+\\.?[0-9]*", text);
println("All numbers: " + allNumbers.join(", "));
// Replace
let sanitized: String = Regex::replaceAll("[0-9]+", text, "###");
println("Anonymized: " + sanitized);
// Split
let csv: String = "alpha; beta;gamma; delta";
let parts: List = Regex::split(";\\s*", csv);
println("Parts: " + parts.join(", "));
return 0;
}
}
uri
import tinox.core.uri;
The uri module provides functions for working with URIs and URLs.
It allows encoding and decoding of URIs (fully or component-wise) as well as
decomposing a URL into its parts: scheme, host, path, query string, and fragment.
The parser result is returned as a UriParts structure.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Uri::encode |
fn encode(s: String) -> String |
Encodes a complete URI (reserved characters like /, ?, # are preserved). |
Uri::decode |
fn decode(s: String) -> String |
Decodes a URI-encoded string. |
Uri::encodeComponent |
fn encodeComponent(s: String) -> String |
Encodes a URI component (reserved characters are also percent-encoded). |
Uri::decodeComponent |
fn decodeComponent(s: String) -> String |
Decodes a percent-encoded URI component. |
Uri::parse |
fn parse(url: String) -> UriParts |
Decomposes a URL into its parts. Returns a UriParts structure
with the fields scheme, host, path,
query, and fragment. If the :// sequence
is missing, all fields are empty.
|
UriParts |
struct { scheme, host, path, query, fragment: String } |
Data structure with the five parts of a decomposed URL. |
Example
import tinox.core.uri;
class Main
{
fnc main() -> Int32
{
// Encoding and decoding
let raw: String = "Search term: Daily fresh!";
let encoded: String = Uri::encodeComponent(raw);
println("Encoded: " + encoded);
println("Decoded: " + Uri::decodeComponent(encoded));
// Encode full URI
let url: String = "https://example.com/search?q=Tinox Docs&lang=en";
println("URI encoded: " + Uri::encode(url));
// Parse URL
let complex: String = "https://api.tinox.dev:8080/v1/users?limit=10&page=2#results";
let parts: UriParts = Uri::parse(complex);
println("Scheme: " + parts.scheme);
println("Host: " + parts.host);
println("Path: " + parts.path);
println("Query: " + parts.query);
println("Fragment: " + parts.fragment);
// Simple URL without path
let simple: UriParts = Uri::parse("ftp://files.example.org");
println("FTP host: " + simple.host);
return 0;
}
}
fs
import tinox.core.fs;
The fs module provides basic filesystem operations. It covers reading, writing, copying, moving, and deleting files as well as creating, listing, and removing directories.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Fs::readFile |
fn readFile(path: String) -> String |
Reads the entire content of the file at path and returns it as a string. |
Fs::writeFile |
fn writeFile(path: String, content: String) -> Nothing |
Writes content to the file at path (overwrites existing contents). |
Fs::appendFile |
fn appendFile(path: String, content: String) -> Nothing |
Appends content to the existing content of the file. |
Fs::deleteFile |
fn deleteFile(path: String) -> Nothing |
Deletes the file at path from the filesystem. |
Fs::copyFile |
fn copyFile(src: String, dest: String) -> Nothing |
Copies the file from src to dest. |
Fs::moveFile |
fn moveFile(src: String, dest: String) -> Nothing |
Moves the file from src to dest (copy + delete). |
Fs::listDirectory |
fn listDirectory(path: String) -> List<String> |
Returns a list of all entries (files and subdirectories) in the specified directory. |
Fs::createDirectory |
fn createDirectory(path: String) -> Nothing |
Creates a new directory at path. |
Fs::deleteDirectory |
fn deleteDirectory(path: String) -> Nothing |
Removes the directory at path (must be empty). |
Example
import tinox.core.fs;
// Create directory and write files
Fs::createDirectory("/tmp/demo");
Fs::writeFile("/tmp/demo/hello.txt", "Hello, Tinox!\n");
Fs::appendFile("/tmp/demo/hello.txt", "Second line.\n");
// Read file and print
let content: String = Fs::readFile("/tmp/demo/hello.txt");
print(content);
// Copy file and delete original (= move)
Fs::copyFile("/tmp/demo/hello.txt", "/tmp/demo/backup.txt");
Fs::deleteFile("/tmp/demo/hello.txt");
// List directory contents
let entries: List<String> = Fs::listDirectory("/tmp/demo");
var i: Int64 = 0;
while i < entries.len()
{
print(entries[i] + "\n");
i = i + 1;
}
// Clean up
Fs::deleteFile("/tmp/demo/backup.txt");
Fs::deleteDirectory("/tmp/demo");
io
import tinox.core.io;
The io module provides I/O utilities: The Io class
offers console output, File represents a file handle for opening,
reading, writing, and closing, Paths enables decomposition and composition
of file paths, and Buffer is used for efficient string accumulation.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Io::newBuffer |
fn newBuffer() -> Buffer |
Creates a new, empty Buffer. |
Io::printLine |
fn printLine(value: String) -> Nothing |
Prints a string followed by a newline to the console. |
Io::printHeader |
fn printHeader(title: String) -> Nothing |
Prints title as a highlighted heading (e.g. === Title ===). |
File::new |
fn new(path: String) -> File |
Creates a new, not yet opened File instance for the specified path. |
File::getPath |
fn getPath() -> String |
Returns the file path of the instance. |
File::close |
fn close() -> Nothing |
Closes the file if it is open and resets the handle. |
File::exists |
fn exists(path: String) -> Bool |
Checks whether a file exists at path. |
Paths::getExtension |
fn getExtension(filename: String) -> String |
Extracts the file extension (without dot) from a filename, e.g. "txt". |
Paths::getFileName |
fn getFileName(filePath: String) -> String |
Extracts the filename from a full path, e.g. "file.txt". |
Paths::getDirectory |
fn getDirectory(filePath: String) -> String |
Returns the directory part of a path, e.g. "/path/to". |
Paths::join |
fn join(dir: String, file: String) -> String |
Joins directory and filename with a slash. |
Buffer::new |
fn new() -> Buffer |
Creates a new, empty buffer. |
Buffer::append |
fn append(s: String) -> Nothing |
Appends a string to the buffer. |
Buffer::appendInt |
fn appendInt(n: Int64) -> Nothing |
Appends the text representation of an integer to the buffer. |
Buffer::appendLine |
fn appendLine(s: String) -> Nothing |
Appends a string with a trailing newline. |
Buffer::clear |
fn clear() -> Nothing |
Clears all buffer content. |
Buffer::toString |
fn toString() -> String |
Returns the accumulated buffer content as a string. |
Buffer::isEmpty |
fn isEmpty() -> Bool |
Returns true if the buffer has no content. |
Example
import tinox.core.io;
// Use path helper functions
let path: String = "/home/user/documents/report.txt";
Io::printLine("Filename : " + Paths::getFileName(path));
Io::printLine("Extension : " + Paths::getExtension(path));
Io::printLine("Directory : " + Paths::getDirectory(path));
let newPath: String = Paths::join("/home/user/export", "output.csv");
Io::printLine("New path: " + newPath);
// Buffer for efficient concatenation
let buf: Buffer = Io::newBuffer();
buf.appendLine("Line 1");
buf.appendLine("Line 2");
buf.append("Number: ");
buf.appendInt(42);
if !buf.isEmpty()
{
Io::printHeader("Buffer contents");
Io::printLine(buf.toString());
}
// Check file handle
let file: File = File::new(path);
if File::exists(path)
{
Io::printLine("File exists: " + file.getPath());
file.close();
}
env
import tinox.core.env;
The env module provides access to environment variables of the running process as well as querying and changing the current working directory.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Env::getVar |
fn getVar(name: String) -> String |
Returns the value of the environment variable name. |
Env::setVar |
fn setVar(name: String, value: String) -> Nothing |
Sets the environment variable name to value. |
Env::removeVar |
fn removeVar(name: String) -> Nothing |
Removes the environment variable name. |
Env::currentDir |
fn currentDir() -> String |
Returns the current working directory of the process. |
Env::setCurrentDir |
fn setCurrentDir(path: String) -> Nothing |
Changes the working directory to path. |
Example
import tinox.core.env;
// Read environment variable
let home: String = Env::getVar("HOME");
print("Home directory: " + home + "\n");
// Set and read new variable
Env::setVar("APP_MODE", "production");
let mode: String = Env::getVar("APP_MODE");
print("Mode: " + mode + "\n");
// Get and change working directory
let cwd: String = Env::currentDir();
print("Current directory: " + cwd + "\n");
Env::setCurrentDir("/tmp");
print("New directory: " + Env::currentDir() + "\n");
// Remove variable again
Env::removeVar("APP_MODE");
print("APP_MODE removed.\n");
process
import tinox.core.process;
The process module provides control functions for the running OS process: exiting with an exit code, sleeping, querying the process ID and command-line arguments, and accessing environment variables.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Process::exit |
fn exit(code: Int64) -> Nothing |
Terminates the process with the specified exit code. |
Process::sleep |
fn sleep(ms: Int64) -> Nothing |
Pauses execution for ms milliseconds. |
Process::pid |
fn pid() -> Int64 |
Returns the process ID (PID) of the running process. |
Process::args |
fn args() -> List<String> |
Returns the command-line arguments as a list. |
Process::env |
fn env(name: String) -> String |
Reads the value of the environment variable name. |
Process::setEnv |
fn setEnv(name: String, value: String) -> Nothing |
Sets the environment variable name to value. |
Example
import tinox.core.process;
// Print PID and arguments
let pid: Int64 = Process::pid();
print("Process ID: " + pid.toString() + "\n");
let args: List<String> = Process::args();
print("Argument count: " + args.len().toString() + "\n");
var i: Int64 = 0;
while i < args.len()
{
print(" arg[" + i.toString() + "] = " + args[i] + "\n");
i = i + 1;
}
// Set and read environment variable
Process::setEnv("TINOX_LOG", "debug");
let logLevel: String = Process::env("TINOX_LOG");
print("Log level: " + logLevel + "\n");
// Brief pause, then clean exit
Process::sleep(500);
print("Program end.\n");
Process::exit(0);
debug
import tinox.core.debug;
The debug module provides tools for runtime diagnostics: assertions, controlled program abort, stack trace output, memory usage query, and manual garbage collection.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Debug::assert |
fn assert(condition: Bool, message: String) -> Nothing |
Throws an exception with message when condition is false. |
Debug::panic |
fn panic(message: String) -> Nothing |
Immediately aborts the program with the message "Assertion failed: " + message. |
Debug::printStackTrace |
fn printStackTrace() -> Nothing |
Prints the current call stack to the console. |
Debug::typeName |
fn typeName() -> String |
Returns the type name of the current object (default "unknown"). |
Debug::memoryUsage |
fn memoryUsage() -> Int64 |
Returns the current memory usage of the runtime environment in bytes. |
Debug::gcCollect |
fn gcCollect() -> Nothing |
Manually triggers a garbage collection run. |
Example
import tinox.core.debug;
// Measure memory usage before work
let before: Int64 = Debug::memoryUsage();
print("Memory before: " + before.toString() + " bytes\n");
// Assertion – fails if condition not met
let value: Int64 = 42;
Debug::assert(value > 0, "Value must be positive");
Debug::assert(value < 100, "Value must not exceed 100");
// Print stack trace for diagnostic purposes
Debug::printStackTrace();
// Force garbage collection and measure memory again
Debug::gcCollect();
let after: Int64 = Debug::memoryUsage();
print("Memory after: " + after.toString() + " bytes\n");
// Intentional abort on unexpected state
let error: Bool = false;
if error
{
Debug::panic("Unexpected system state");
}
print("All checks passed.\n");
zip
import tinox.core.zip;
The zip module enables creating, opening, and editing ZIP archives.
Files can be added, extracted, and removed. Entries are described via
ZipEntry objects with name and size; the archive itself is represented by
ZipArchive.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Zip::open |
fn open(path: String) -> ZipArchive |
Opens an existing ZIP archive and reads its entries. |
Zip::create |
fn create(path: String) -> ZipArchive |
Creates a new, empty ZIP archive at the specified path. |
Zip::listEntries |
fn listEntries(path: String) -> List<ZipEntry> |
Returns all entries of an archive as a list of ZipEntry objects. |
Zip::extractFile |
fn extractFile(archive: ZipArchive, entryName: String) -> String |
Extracts the content of an archive entry as a string. |
Zip::addFile |
fn addFile(archive: ZipArchive, name: String, content: String) -> Nothing |
Adds a new file with the specified name and content to the archive. |
Zip::removeFile |
fn removeFile(archive: ZipArchive, entryName: String) -> Nothing |
Removes an entry from the archive. |
Zip::close |
fn close(archive: ZipArchive) -> Nothing |
Closes the archive (final operation after editing). |
ZipArchive |
path: String; entries: List<ZipEntry> |
Data structure: represents a ZIP archive with path and entry list. |
ZipEntry |
name: String; size: Int64 |
Data structure: describes a single archive entry with name and size in bytes. |
Example
import tinox.core.zip;
// Create new archive and add files
let archive: ZipArchive = Zip::create("/tmp/package.zip");
Zip::addFile(archive, "README.txt", "This is a Tinox demo.\n");
Zip::addFile(archive, "data.csv", "name,value\nalpha,1\nbeta,2\n");
Zip::addFile(archive, "config.json", "{\"version\": 1}\n");
// List entries
let entries: List<ZipEntry> = archive.entries;
var i: Int64 = 0;
while i < entries.len()
{
let e: ZipEntry = entries[i];
print(e.name + " (" + e.size.toString() + " bytes)\n");
i = i + 1;
}
// Extract content of a file
let content: String = Zip::extractFile(archive, "README.txt");
print("README: " + content);
// Remove entry
Zip::removeFile(archive, "config.json");
print("Entries after deletion: " + archive.entries.len().toString() + "\n");
Zip::close(archive);
compress
import tinox.core.compress;
The compress module provides general-purpose byte compression for
arbitrary payloads (log shipping, HTTP response bodies, message-queue payload
compression, ...) — unlike zip's Zip, which is
STORED-only (no compression). Two formats are supported: gzip
(RFC 1952 — magic bytes, header, deflate stream, then a CRC32 + size trailer that
decompression verifies automatically; the format most external tools/HTTP
Content-Encoding: gzip expect) and raw DEFLATE
(RFC 1951, no header/trailer — smallest possible framing, the same on-wire format
the websocket module's permessage-deflate already uses). Prefer
gzip/gunzip unless framing size specifically matters and
both ends already agree out-of-band on the format.
Decompression failure (truncated/malformed input, a CRC32/trailer mismatch, or a
payload exceeding a 16MB decompression-bomb cap) is reported via
lastGunzipOk()/lastInflateOk() rather than a silent
empty-but-"successful" result — check it immediately after calling
gunzip()/inflateRaw().
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Compress::gzip |
fnc gzip(bytes: List<Int64>) -> List<Int64> |
Compresses bytes into a gzip (RFC 1952) container. |
Compress::gunzip |
fnc gunzip(bytes: List<Int64>) -> List<Int64> |
Decompresses a gzip container back to the original bytes. On failure,
returns an empty list and lastGunzipOk() reports false. |
Compress::lastGunzipOk |
fnc lastGunzipOk() -> Bool |
Whether the most recent gunzip() call on this thread
succeeded. Check immediately after calling gunzip(). |
Compress::deflateRaw |
fnc deflateRaw(bytes: List<Int64>) -> List<Int64> |
Compresses bytes with raw DEFLATE (RFC 1951, no gzip header/trailer). |
Compress::inflateRaw |
fnc inflateRaw(bytes: List<Int64>) -> List<Int64> |
Decompresses a raw DEFLATE stream. On failure, returns an empty list and
lastInflateOk() reports false. |
Compress::lastInflateOk |
fnc lastInflateOk() -> Bool |
Whether the most recent inflateRaw() call on this thread
succeeded. Check immediately after calling inflateRaw(). |
Example
import tinox.core.compress;
let msg: String = "hello, tinox compress module!";
var bytes: List<Int64> = [];
var i: Int64 = 0;
while i < msg.len()
{
bytes.push(msg.charCodeAt(i));
i = i + 1;
}
// gzip round trip
let packed: List<Int64> = Compress::gzip(bytes);
let restored: List<Int64> = Compress::gunzip(packed);
if !Compress::lastGunzipOk()
{
throw "corrupt gzip stream";
}
print("compressed " + bytes.len().toString() + " bytes down to " + packed.len().toString() + "\n");
asm
import tinox.core.asm;
The asm module provides a simple bytecode assembler. Via the
Assembler class, opcodes and 64-bit integer values are written into a byte buffer,
labels are set and resolved, and the finished bytecode is exported as a byte string.
The Ops class defines all available opcodes as integer constants.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Assembler::new |
fn new() -> Assembler |
Creates a new, empty assembler instance with empty bytecode and label map. |
Assembler::emit |
fn emit(asm: Assembler, op: Int64) -> Nothing |
Appends a single byte (opcode) to the bytecode. |
Assembler::emitInt |
fn emitInt(asm: Assembler, value: Int64) -> Nothing |
Writes a 64-bit integer value little-endian (8 bytes) into the bytecode. |
Assembler::emitLabel |
fn emitLabel(asm: Assembler, name: String) -> Nothing |
Registers a label at the current bytecode position. |
Assembler::resolveLabels |
fn resolveLabels(asm: Assembler) -> List<Int64> |
Returns the current bytecode (after label resolution) as an integer list. |
Assembler::toBytes |
fn toBytes(asm: Assembler) -> String |
Converts the bytecode into a raw byte string (one character per byte). |
Ops::Nop |
fn Nop() -> Int64 |
Opcode 0 – No operation. |
Ops::Halt |
fn Halt() -> Int64 |
Opcode 1 – Halt program. |
Ops::Load |
fn Load() -> Int64 |
Opcode 2 – Load value from memory. |
Ops::Store |
fn Store() -> Int64 |
Opcode 3 – Store value in memory. |
Ops::Push |
fn Push() -> Int64 |
Opcode 4 – Push value onto stack. |
Ops::Pop |
fn Pop() -> Int64 |
Opcode 5 – Pop value from stack. |
Ops::Add |
fn Add() -> Int64 |
Opcode 6 – Addition. |
Ops::Sub |
fn Sub() -> Int64 |
Opcode 7 – Subtraction. |
Ops::Mul |
fn Mul() -> Int64 |
Opcode 8 – Multiplication. |
Ops::Div |
fn Div() -> Int64 |
Opcode 9 – Division. |
Ops::Jmp |
fn Jmp() -> Int64 |
Opcode 10 – Unconditional jump. |
Ops::Jz |
fn Jz() -> Int64 |
Opcode 11 – Jump if zero. |
Ops::Jnz |
fn Jnz() -> Int64 |
Opcode 12 – Jump if not zero. |
Ops::Cmp |
fn Cmp() -> Int64 |
Opcode 13 – Compare two values. |
Ops::Call |
fn Call() -> Int64 |
Opcode 14 – Call subroutine. |
Ops::Ret |
fn Ret() -> Int64 |
Opcode 15 – Return from subroutine. |
Example
import tinox.core.asm;
// Create assembler
let a: Assembler = Assembler::new();
// Simple program: push 7, push 3, add, halt
Assembler::emit(a, Ops::Push());
Assembler::emitInt(a, 7);
Assembler::emit(a, Ops::Push());
Assembler::emitInt(a, 3);
Assembler::emitLabel(a, "add_start");
Assembler::emit(a, Ops::Add());
// Conditional jump back to loop (demo)
Assembler::emit(a, Ops::Jz());
Assembler::emitInt(a, 0);
Assembler::emit(a, Ops::Halt());
// Resolve labels
let code: List<Int64> = Assembler::resolveLabels(a);
print("Bytecode length: " + code.len().toString() + " bytes\n");
// Export as byte string
let bytes: String = Assembler::toBytes(a);
print("Raw data length: " + bytes.len().toString() + "\n");
bitmap
import tinox.core.bitmap;
The bitmap module provides a simple 2D pixel graphics library.
Bitmaps are managed as grids of 32-bit color values (ARGB/RGB as Int64).
Supported operations include creating, reading and writing pixels, filling
rectangles, and drawing lines and circles (Bresenham algorithms).
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Bitmap::create |
fn create(width: Int64, height: Int64, color: Int64) -> Bitmap |
Creates a new bitmap with the specified dimensions, all pixels set to color. |
Bitmap::getPixel |
fn getPixel(bitmap: Bitmap, x: Int64, y: Int64) -> Int64 |
Returns the color value of the pixel at position (x, y); out of bounds: 0. |
Bitmap::setPixel |
fn setPixel(bitmap: Bitmap, x: Int64, y: Int64, color: Int64) -> Nothing |
Sets the pixel at (x, y) to color; out of bounds: no effect. |
Bitmap::fillRect |
fn fillRect(bitmap: Bitmap, x: Int64, y: Int64, w: Int64, h: Int64, color: Int64) -> Nothing |
Fills a rectangle starting at (x, y) with width w and height h in color. |
Bitmap::drawLine |
fn drawLine(bitmap: Bitmap, x0: Int64, y0: Int64, x1: Int64, y1: Int64, color: Int64) -> Nothing |
Draws a line from (x0, y0) to (x1, y1) using the Bresenham algorithm. |
Bitmap::drawCircle |
fn drawCircle(bitmap: Bitmap, cx: Int64, cy: Int64, radius: Int64, color: Int64) -> Nothing |
Draws a circle with center (cx, cy) and the specified radius (midpoint algorithm). |
Example
import tinox.core.bitmap;
// Colors as ARGB hex values
let white: Int64 = 0xFFFFFFFF;
let black: Int64 = 0xFF000000;
let red: Int64 = 0xFFFF0000;
let blue: Int64 = 0xFF0000FF;
// Create 200×200 pixel white bitmap
let img: Bitmap = Bitmap::create(200, 200, white);
// Draw black border (four lines)
Bitmap::drawLine(img, 0, 0, 199, 0, black);
Bitmap::drawLine(img, 199, 0, 199, 199, black);
Bitmap::drawLine(img, 199, 199, 0, 199, black);
Bitmap::drawLine(img, 0, 199, 0, 0, black);
// Red filled rectangle in the center
Bitmap::fillRect(img, 50, 50, 100, 100, red);
// Blue circle over the rectangle
Bitmap::drawCircle(img, 100, 100, 40, blue);
// Read and print single pixel
let px: Int64 = Bitmap::getPixel(img, 100, 100);
print("Pixel (100,100): " + px.toString() + "\n");
// Overwrite single pixel
Bitmap::setPixel(img, 100, 100, black);
print("Pixel after overwrite: " + Bitmap::getPixel(img, 100, 100).toString() + "\n");
tpl
import tinox.core.tpl;
The tpl module provides a simple Mustache-style template engine.
Templates use {{key}} placeholders that are replaced with data values.
In addition to direct rendering, templates can be pre-compiled
(compile) and then efficiently executed repeatedly (execute).
A loop function enables rendering a template for multiple data records.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Template::render |
fn render(template: String, data: Map<String, JsonValue>) -> String |
Replaces all {{key}} placeholders in template with the corresponding values from data. |
Template::renderLoop |
fn renderLoop(template: String, items: List<Map<String, JsonValue>>, itemVar: String) -> String |
Renders template for each element in items and concatenates the results. |
Template::compile |
fn compile(template: String) -> CompiledTemplate |
Parses template into a list of TemplateSection segments (text / var / section / end). |
Template::execute |
fn execute(tpl: CompiledTemplate, data: Map<String, JsonValue>) -> String |
Executes an already compiled template with the specified data. |
CompiledTemplate |
source: String; sections: List<TemplateSection> |
Data structure: pre-compiled template with source text and section list. |
TemplateSection |
kind: String; content: String |
Data structure: a section of the template; kind is "text", "var", "section", or "end". |
Example
import tinox.core.tpl;
// Simple direct rendering
let template: String = "Hello, {{name}}! You have {{points}} points.";
let data: Map<String, JsonValue> = Map::new();
data["name"] = JsonValue::fromString("Maria");
data["points"] = JsonValue::fromInt(1340);
let output: String = Template::render(template, data);
print(output + "\n");
// Output: Hello, Maria! You have 1340 points.
// Template loop over multiple records
let rowTemplate: String = "- {{title}} ({{year}})\n";
let movies: List<Map<String, JsonValue>> = [];
let movie1: Map<String, JsonValue> = Map::new();
movie1["title"] = JsonValue::fromString("Inception");
movie1["year"] = JsonValue::fromInt(2010);
movies.push(movie1);
let movie2: Map<String, JsonValue> = Map::new();
movie2["title"] = JsonValue::fromString("Dune");
movie2["year"] = JsonValue::fromInt(2021);
movies.push(movie2);
let list: String = Template::renderLoop(rowTemplate, movies, "movie");
print(list);
// Pre-compiled template for reuse
let compiled: CompiledTemplate = Template::compile("User: {{login}} | Role: {{role}}");
let admin: Map<String, JsonValue> = Map::new();
admin["login"] = JsonValue::fromString("admin");
admin["role"] = JsonValue::fromString("Administrator");
print(Template::execute(compiled, admin) + "\n");
crypto
import tinox.core.crypto;
The crypto module provides basic cryptographic functions: hashing (MD5, SHA-256), HMAC signatures, symmetric AES encryption, secure key generation, and password-based key derivation via PBKDF2.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Crypto::md5 | fn md5(data: String) -> String | Computes the MD5 hash of the given string and returns it as a hex string. |
Crypto::sha256 | fn sha256(data: String) -> String | Computes the SHA-256 hash of the given string and returns it as a hex string. |
Crypto::hmacSha256 | fn hmacSha256(data: String, key: String) -> String | Generates an HMAC-SHA-256 signature of the data with the specified key. |
Crypto::aesEncrypt | fn aesEncrypt(data: String, key: String) -> String | Encrypts the data with AES-256-GCM (authenticated). key may be any length (derived internally into a 256-bit key via SHA-256). Returns hex-encoded nonce + ciphertext + auth tag, with a fresh random nonce per call — encrypting the same data twice yields different results. Throws on failure (e.g. runtime built without OpenSSL). |
Crypto::aesDecrypt | fn aesDecrypt(data: String, key: String) -> String | Decrypts data produced by aesEncrypt. Throws on a wrong key or tampered/corrupted data (GCM authentication fails) instead of silently returning incorrect plaintext. |
Crypto::generateKey | fn generateKey(length: Int64) -> String | Generates a cryptographically random key of the desired byte length. |
Crypto::pbkdf2 | fn pbkdf2(password: String, salt: String, iterations: Int64, keyLength: Int64) -> String | Derives a key from a password (PBKDF2 with SHA-256 iterations). Returns the first keyLength characters of the derived key. |
Crypto::secureRandomBytes | fn secureRandomBytes(n: Int64) -> List<Int64> | Generates n cryptographically secure random bytes (OpenSSL RAND_bytes) as a raw byte list — unlike generateKey, no hex/string detour, so it's directly usable for PKCE code_verifier & co. (see the oauth2 module, issue #131). Throws on failure instead of returning a too-short list. |
Example
import tinox.core.crypto;
// Simple SHA-256 hash
let digest: String = Crypto::sha256("Hello World");
println("SHA-256: " + digest);
// Verify HMAC signature
let secret: String = "mySecret";
let sig: String = Crypto::hmacSha256("payload", secret);
println("HMAC: " + sig);
// Generate random AES key and encrypt data
let key: String = Crypto::generateKey(32);
let cipher: String = Crypto::aesEncrypt("Secret message", key);
let plain: String = Crypto::aesDecrypt(cipher, key);
println("Decrypted: " + plain);
// Password hashing via PBKDF2
let salt: String = Crypto::generateKey(16);
let derived: String = Crypto::pbkdf2("password123", salt, 10000, 32);
println("Derived key: " + derived);
hash
import tinox.core.hash;
The hash module implements fast, non-cryptographic hash functions for strings and integers, as well as a combine function to fold multiple hashes into a single value. Typical use cases include hash maps, checksums, and fast data comparisons.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Hash::hashString | fn hashString(s: String) -> Int64 | Computes a djb2-style hash for the given string. |
Hash::hashInt | fn hashInt(n: Int64) -> Int64 | Computes a hash for an integer value using bit-mixing operations. |
Hash::combine | fn combine(h1: Int64, h2: Int64) -> Int64 | Combines two hash values into a single value (useful for composite keys). |
Example
import tinox.core.hash;
// Compute string hash
let h1: Int64 = Hash::hashString("Tinox");
println("String hash: " + h1.toString());
// Compute integer hash
let h2: Int64 = Hash::hashInt(42);
println("Int hash: " + h2.toString());
// Hash composite key (e.g. (name, id))
let nameHash: Int64 = Hash::hashString("Alice");
let idHash: Int64 = Hash::hashInt(1001);
let combined: Int64 = Hash::combine(nameHash, idHash);
println("Combined hash: " + combined.toString());
// Simple bucket assignment
let buckets: Int64 = 16;
let bucket: Int64 = combined % buckets;
println("Bucket index: " + bucket.toString());
validation
import tinox.core.validation;
The validation module provides helper functions for input validation. It allows checking strings against common formats (email, URL, phone number), enforcing length rules, and applying arbitrary regular expressions.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Validation::isEmail | fn isEmail(s: String) -> Bool | Checks whether the string matches the format of an email address. |
Validation::isUrl | fn isUrl(s: String) -> Bool | Checks whether the string is a valid HTTP or HTTPS URL. |
Validation::isNumeric | fn isNumeric(s: String) -> Bool | Checks whether the string consists exclusively of digits (0–9). |
Validation::isAlpha | fn isAlpha(s: String) -> Bool | Checks whether the string consists exclusively of letters (a–z, A–Z). |
Validation::isAlphanumeric | fn isAlphanumeric(s: String) -> Bool | Checks whether the string contains only letters and digits. |
Validation::isPhone | fn isPhone(s: String) -> Bool | Checks whether the string has a valid phone number format (digits, +, -, spaces, brackets). |
Validation::minLength | fn minLength(s: String, min: Int64) -> Bool | Checks whether the string is at least min characters long. |
Validation::maxLength | fn maxLength(s: String, max: Int64) -> Bool | Checks whether the string is at most max characters long. |
Validation::isInRange | fn isInRange(n: Int64, min: Int64, max: Int64) -> Bool | Checks whether the integer value n is within the range [min, max]. |
Validation::matchesPattern | fn matchesPattern(s: String, pattern: String) -> Bool | Checks whether the string matches the specified regular expression. |
Example
import tinox.core.validation;
let email: String = "user@example.com";
if Validation::isEmail(email)
{
println("Valid email address.");
}
let url: String = "https://tinox.io/api";
if Validation::isUrl(url) == false
{
println("Invalid URL.");
}
// Check password rules
let password: String = "Secret42!";
let minOk: Bool = Validation::minLength(password, 8);
let maxOk: Bool = Validation::maxLength(password, 64);
println("Length OK: " + minOk.toString());
// Check numeric input
let age: String = "27";
if Validation::isNumeric(age)
{
let n: Int64 = (Int64)age;
println("Age in range: " + Validation::isInRange(n, 0, 120).toString());
}
// Custom pattern
let zip: String = "80331";
println("ZIP format: " + Validation::matchesPattern(zip, "^[0-9]{5}$").toString());
uuid
import tinox.core.uuid;
The uuid module enables generating and validating UUIDs (Universally Unique Identifiers) in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. It provides conversion functions between string representation and internal Uuid objects.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Uuid::generate | fn generate() -> String | Generates a new, random UUID and returns it as a string. |
Uuid::fromString | fn fromString(s: String) -> Uuid | Creates a Uuid object from a UUID string. |
Uuid::toString | fn toString() -> String | Returns the UUID of the object as a string. |
Uuid::isValid | fn isValid(s: String) -> Bool | Checks whether the string is a syntactically correct UUID in the standard format (36 characters, hex + hyphens). |
Example
import tinox.core.uuid;
// Generate new UUID
let id: String = Uuid::generate();
println("New UUID: " + id);
// Validate a UUID
let input: String = "123e4567-e89b-12d3-a456-426614174000";
if Uuid::isValid(input)
{
println("Valid UUID.");
let obj: Uuid = Uuid::fromString(input);
println("UUID object: " + obj.toString());
}
else
{
println("Invalid UUID input.");
}
// Typical use as record ID
let recordId: String = Uuid::generate();
println("New record with ID: " + recordId);
semaphore
import tinox.core.semaphore;
The semaphore module provides synchronization primitives for concurrent programs: a counting Semaphore to limit simultaneous accesses, an exclusive Mutex, and an RWLock for separate read and write locks.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Semaphore::new | fn new(count: Int64) -> Semaphore | Creates a new semaphore with the specified initial counter. |
Semaphore::acquire | fn acquire(sem: Semaphore) -> Nothing | Decrements the counter by 1. Throws an error if the counter is already 0. |
Semaphore::release | fn release(sem: Semaphore) -> Nothing | Increments the counter by 1, releasing a resource. |
Semaphore::tryAcquire | fn tryAcquire(sem: Semaphore) -> Bool | Attempts to decrement the counter; returns true on success without throwing. |
Mutex::new | fn new() -> Mutex | Creates a new, unlocked mutex. |
Mutex::lock | fn lock(mutex: Mutex) -> Nothing | Locks the mutex. Throws if it is already locked. |
Mutex::unlock | fn unlock(mutex: Mutex) -> Nothing | Releases the mutex. |
Mutex::tryLock | fn tryLock(mutex: Mutex) -> Bool | Attempts to lock the mutex; returns true on success without throwing. |
RWLock::new | fn new() -> RWLock | Creates a new RWLock with no active locks. |
RWLock::readLock | fn readLock(lock: RWLock) -> Nothing | Acquires a read lock. Throws if a write lock is active. |
RWLock::readUnlock | fn readUnlock(lock: RWLock) -> Nothing | Releases a previously acquired read lock. |
RWLock::writeLock | fn writeLock(lock: RWLock) -> Nothing | Acquires an exclusive write lock. |
RWLock::writeUnlock | fn writeUnlock(lock: RWLock) -> Nothing | Releases a previously acquired write lock. |
Example
import tinox.core.semaphore;
// Semaphore: maximum 3 simultaneous connections
let sem: Semaphore = Semaphore::new(3);
if Semaphore::tryAcquire(sem)
{
println("Connection acquired.");
// ... work ...
Semaphore::release(sem);
}
// Mutex: protect critical section
let mtx: Mutex = Mutex::new();
Mutex::lock(mtx);
println("Critical section");
Mutex::unlock(mtx);
// RWLock: multiple readers, exclusive writer
let rw: RWLock = RWLock::new();
RWLock::readLock(rw);
println("Reading data ...");
RWLock::readUnlock(rw);
RWLock::writeLock(rw);
println("Writing data ...");
RWLock::writeUnlock(rw);
pool
import tinox.core.pool;
The pool module provides a generic object pool. Expensive objects (e.g. database connections, network sockets) are kept in the pool and reused instead of being created anew each time. The maximum pool size is configurable.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Pool::new | fn new(maxSize: Int64) -> Pool<T> | Creates a new pool with the specified maximum size. |
Pool::acquire | fn acquire(pool: Pool<T>) -> T | Returns an available object from the pool or creates a new one if the maximum size has not been reached. Throws "Pool exhausted" when the pool is full. |
Pool::release | fn release(pool: Pool<T>, obj: T) -> Nothing | Returns a used object to the pool so it can be reused. |
Pool::clear | fn clear(pool: Pool<T>) -> Nothing | Removes all objects from the pool (available and in-use ones). |
Example
import tinox.core.pool;
// Pool for database connections (maximum 5)
let dbPool: Pool<DbConnection> = Pool::new(5);
// Acquire connection
let conn: DbConnection = Pool::acquire(dbPool);
let result: List<Row> = conn.query("SELECT * FROM users");
println("Rows: " + result.len().toString());
// Release connection
Pool::release(dbPool, conn);
// Second connection – reused from pool
let conn2: DbConnection = Pool::acquire(dbPool);
conn2.execute("UPDATE users SET active = 1 WHERE id = 42");
Pool::release(dbPool, conn2);
// Clear pool (e.g. on shutdown)
Pool::clear(dbPool);
println("Pool cleared.");
ratelimit
import tinox.core.ratelimit;
The ratelimit module implements two request limiting strategies: RateLimiter uses a sliding-window approach (fixed time window), while TokenBucket uses the token bucket approach with a configurable refill rate.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
RateLimiter::new | fn new(maxRequests: Int64, windowMs: Int64) -> RateLimiter | Creates a new rate limiter with maximum request count and time window in milliseconds. |
RateLimiter::allow | fn allow(limiter: RateLimiter) -> Bool | Checks whether another request is allowed. Returns true and registers the request, or false if the limit is reached. |
RateLimiter::reset | fn reset(limiter: RateLimiter) -> Nothing | Resets all registered requests. |
RateLimiter::remaining | fn remaining(limiter: RateLimiter) -> Int64 | Returns the number of remaining allowed requests in the current time window. |
TokenBucket::new | fn new(capacity: Int64, refillRate: Float64) -> TokenBucket | Creates a token bucket with capacity and refill rate (tokens per second). |
TokenBucket::allow | fn allow(bucket: TokenBucket) -> Bool | Checks whether a token is available. Refills first and returns true if a token could be consumed. |
TokenBucket::refill | fn refill(bucket: TokenBucket) -> Nothing | Refills the bucket based on the time elapsed since the last refill. |
Example
import tinox.core.ratelimit;
// Sliding window: 100 requests per minute
let limiter: RateLimiter = RateLimiter::new(100, 60000);
var i: Int64 = 0;
while i < 5
{
if RateLimiter::allow(limiter)
{
println("Request " + i.toString() + " allowed. Remaining: "
+ RateLimiter::remaining(limiter).toString());
}
i = i + 1;
}
// Token bucket: capacity 10, 2 tokens/second
let bucket: TokenBucket = TokenBucket::new(10, 2.0);
if TokenBucket::allow(bucket)
{
println("Request via token bucket allowed.");
}
else
{
println("Token bucket empty – request rejected.");
}
cron
import tinox.core.cron;
The cron module enables scheduling recurring tasks via cron expressions in Unix format (5 fields: minute, hour, day, month, weekday). The CronScheduler manages multiple jobs and executes them on tick calls when the condition is met.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Cron::parse | fn parse(expr: String) -> CronExpr | Parses a cron expression (5 space-separated fields) into a CronExpr object. Throws on invalid format. |
Cron::nextRun | fn nextRun(expr: CronExpr) -> Int64 | Calculates the timestamp (ms since epoch) of the next scheduled run. |
Cron::nextMinute | fn nextMinute(current: Int64, field: String) -> Int64 | Helper function: returns the next minute matching the minute field. |
Cron::getCurrentMinute | fn getCurrentMinute(sec: Int64) -> Int64 | Helper function: calculates the current minute (0–59) from a Unix timestamp in seconds. |
Cron::matches | fn matches(expr: CronExpr, timestamp: Int64) -> Bool | Checks whether a cron expression applies to the specified timestamp. |
CronScheduler::new | fn new() -> CronScheduler | Creates a new, stopped scheduler with no jobs. |
CronScheduler::addJob | fn addJob(scheduler: CronScheduler, expr: String, callback: fnc() -> Nothing) -> Nothing | Registers a new job with a cron expression and callback function. |
CronScheduler::start | fn start(scheduler: CronScheduler) -> Nothing | Activates the scheduler so that tick calls execute jobs. |
CronScheduler::stop | fn stop(scheduler: CronScheduler) -> Nothing | Deactivates the scheduler; further tick calls are ignored. |
CronScheduler::tick | fn tick(scheduler: CronScheduler) -> Nothing | Checks all registered jobs against the current timestamp and executes any that are due. |
Example
import tinox.core.cron;
let scheduler: CronScheduler = CronScheduler::new();
// Execute every minute
CronScheduler::addJob(scheduler, "* * * * *", () => {
println("Minutely job running.");
});
// Execute daily at 08:30
CronScheduler::addJob(scheduler, "30 8 * * *", () => {
println("Daily morning report is being generated.");
});
CronScheduler::start(scheduler);
// Main loop (call tick e.g. every 60 s)
var tick: Int64 = 0;
while tick < 3
{
CronScheduler::tick(scheduler);
tick = tick + 1;
}
CronScheduler::stop(scheduler);
println("Scheduler stopped.");
events
import tinox.core.events;
The events module implements the observer pattern via an EventEmitter. Listeners can be registered for named events, subscribed once, or removed again. When an event is emitted, all registered handlers are called with a JsonValue payload.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
EventEmitter::new | fn new() -> EventEmitter | Creates a new, empty event emitter. |
EventEmitter::on | fn on(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> Nothing | Registers a permanent listener for the specified event. |
EventEmitter::once | fn once(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> Nothing | Registers a listener that is executed the first time the event occurs. |
EventEmitter::emit | fn emit(emitter: EventEmitter, event: String, data: JsonValue) -> Nothing | Emits the specified event and passes the payload to all registered handlers. |
EventEmitter::removeListener | fn removeListener(emitter: EventEmitter, event: String, handler: fnc(JsonValue) -> Nothing) -> Nothing | Removes a specific listener for an event. |
EventEmitter::removeAllListeners | fn removeAllListeners(emitter: EventEmitter, event: String) -> Nothing | Removes all listeners for the specified event. |
EventEmitter::listenerCount | fn listenerCount(emitter: EventEmitter, event: String) -> Int64 | Returns the number of registered listeners for an event. |
Example
import tinox.core.events;
let emitter: EventEmitter = EventEmitter::new();
// Permanent listener
EventEmitter::on(emitter, "order", (data: JsonValue) => {
println("New order: " + data.toString());
});
// One-time listener
EventEmitter::once(emitter, "start", (data: JsonValue) => {
println("Application started.");
});
println("Listener count for 'order': "
+ EventEmitter::listenerCount(emitter, "order").toString());
// Emit events
EventEmitter::emit(emitter, "start", Json::null());
EventEmitter::emit(emitter, "order", Json::parse("{\"id\": 42}"));
// Remove all listeners
EventEmitter::removeAllListeners(emitter, "order");
println("Listeners after removal: "
+ EventEmitter::listenerCount(emitter, "order").toString());
time
import tinox.core.time;
The time module provides utilities for time operations: The Time class is the entry-point factory, Duration represents time spans, Timer measures a defined start-stop interval, and Stopwatch is the counterpart with an additional isRunning() query.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Time::now | fn now() -> Int64 | Returns the current system time in milliseconds since the Unix epoch. |
Time::sleep | fn sleep(ms: Int64) -> Nothing | Pauses execution for the specified number of milliseconds. |
Time::newTimer | fn newTimer() -> Timer | Creates a new Timer instance. |
Time::newStopwatch | fn newStopwatch() -> Stopwatch | Creates a new Stopwatch instance. |
Time::newDuration | fn newDuration(seconds: Int64) -> Duration | Creates a Duration from seconds. |
Time::durationFromMinutes | fn durationFromMinutes(minutes: Int64) -> Duration | Creates a Duration from minutes. |
Time::durationFromHours | fn durationFromHours(hours: Int64) -> Duration | Creates a Duration from hours. |
Duration::new | fn new(seconds: Int64) -> Duration | Creates a Duration instance directly from seconds. |
Duration::fromMinutes | fn fromMinutes(minutes: Int64) -> Duration | Creates a Duration from minutes (converted to seconds internally). |
Duration::fromHours | fn fromHours(hours: Int64) -> Duration | Creates a Duration from hours (converted to seconds internally). |
Duration::getSeconds | fn getSeconds() -> Int64 | Returns the duration in seconds. |
Duration::getMinutes | fn getMinutes() -> Int64 | Returns the duration in whole minutes (truncated). |
Duration::getHours | fn getHours() -> Int64 | Returns the duration in whole hours (truncated). |
Duration::add | fn add(other: Duration) -> Duration | Adds another Duration and returns a new instance. |
Duration::sub | fn sub(other: Duration) -> Duration | Subtracts a Duration and returns a new instance. |
Timer::new | fn new() -> Timer | Creates a new, not-yet-started timer. |
Timer::start | fn start() -> Nothing | Starts the timer and saves the current time. |
Timer::stop | fn stop() -> Nothing | Stops the timer and saves the end time. |
Timer::elapsed | fn elapsed() -> Int64 | Returns the elapsed time in milliseconds. If not yet stopped, measures time until now. |
Timer::elapsedSeconds | fn elapsedSeconds() -> Int64 | Returns the elapsed time in whole seconds. |
Stopwatch::new | fn new() -> Stopwatch | Creates a new, not-yet-running stopwatch. |
Stopwatch::start | fn start() -> Nothing | Starts the stopwatch. |
Stopwatch::stop | fn stop() -> Nothing | Stops the stopwatch. |
Stopwatch::isRunning | fn isRunning() -> Bool | Returns true if the stopwatch is currently running. |
Stopwatch::elapsed | fn elapsed() -> Int64 | Returns the elapsed time in milliseconds: while running, the time since start(); after stop(), the frozen start-to-stop span; otherwise 0. |
Example
import tinox.core.time;
// Current time
let now: Int64 = Time::now();
println("Now (ms): " + now.toString());
// Timer for an operation
let timer: Timer = Time::newTimer();
timer.start();
// ... work ...
timer.stop();
println("Duration: " + timer.elapsedSeconds().toString() + " s");
// Stopwatch
let sw: Stopwatch = Time::newStopwatch();
sw.start();
println("Running: " + sw.isRunning().toString());
sw.stop();
println("Elapsed ms: " + sw.elapsed().toString());
// Combine durations
let d1: Duration = Time::newDuration(3600); // 1 hour
let d2: Duration = Time::durationFromMinutes(30);
let total: Duration = d1.add(d2);
println("Total in minutes: " + total.getMinutes().toString());
cache
import tinox.core.cache;
The cache module provides a generic LRU cache (Least Recently Used). When the maximum size is reached, the least recently used entry is automatically evicted. The LruCache class is a slim wrapper around the internal Cache class.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Cache::new | fn new(maxSize: Int64) -> Cache<K, V> | Creates a new cache with the specified maximum number of entries. |
Cache::get | fn get(cache: Cache<K, V>, key: K) -> Option<V> | Returns the value for the key as an Option and updates the access order. |
Cache::set | fn set(cache: Cache<K, V>, key: K, value: V) -> Nothing | Sets an entry. If the maximum size is exceeded, the oldest entry is removed. |
Cache::remove | fn remove(cache: Cache<K, V>, key: K) -> Bool | Removes the entry with the specified key. Returns true if the key was present. |
Cache::clear | fn clear(cache: Cache<K, V>) -> Nothing | Removes all entries from the cache. |
Cache::touch | fn touch(cache: Cache<K, V>, key: K) -> Nothing | Marks a key as most recently used (moves it to the end of the LRU list). |
LruCache::new | fn new(maxSize: Int64) -> LruCache<K, V> | Creates a new LRU cache wrapper with the specified maximum size. |
LruCache::get | fn get(cache: LruCache<K, V>, key: K) -> Option<V> | Returns the value for the key as an Option. |
LruCache::set | fn set(cache: LruCache<K, V>, key: K, value: V) -> Nothing | Sets an entry in the LRU cache. |
Example
import tinox.core.cache;
// LRU cache with maximum 3 entries
let lru: LruCache<String, String> = LruCache::new(3);
LruCache::set(lru, "a", "Apple");
LruCache::set(lru, "b", "Banana");
LruCache::set(lru, "c", "Cherry");
// Read entry
let val: Option<String> = LruCache::get(lru, "b");
if val.isSome()
{
println("Found: " + val.unwrap());
}
// Fourth entry evicts the oldest (here "a")
LruCache::set(lru, "d", "Date");
let old: Option<String> = LruCache::get(lru, "a");
println("'a' still in cache: " + old.isSome().toString());
// Clear cache
Cache::clear(lru.cache);
println("Cache cleared.");
option
import tinox.core.option;
The option module provides the generic type Option<T>, which represents either a value (Some) or the absence of a value (None). Internally a List<T> is used to avoid the need for a null value for T.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Option::none | fn none() -> Option<T> | Creates an empty option (no value). |
Option::some | fn some(value: T) -> Option<T> | Creates an option containing the specified value. |
Option::isSome | fn isSome() -> Bool | Returns true if the option contains a value. |
Option::isNone | fn isNone() -> Bool | Returns true if the option is empty. |
Option::unwrap | fn unwrap() -> T | Returns the contained value. Throws "Option is None" if the option is empty. |
Option::unwrapOr | fn unwrapOr(defaultValue: T) -> T | Returns the contained value or the specified default value if the option is empty. |
Option::map | fn map<U>(transform: fnc(T) -> U) -> Option<U> | Applies a transformation function to the contained value and returns a new option. |
Option::andThen | fn andThen<U>(transform: fnc(T) -> Option<U>) -> Option<U> | Chaining: applies a function that itself returns an option (flatmap). |
Option::orElse | fn orElse(fallback: fnc() -> Option<T>) -> Option<T> | Returns the current option if it has a value; otherwise the result of the fallback call. |
Example
import tinox.core.option;
class Main
{
fnc findUser(id: Int64) -> Option<String>
{
if id == 1
{
return Option::some("Alice");
}
return Option::none();
}
fnc main() -> Int32
{
// Extract value with fallback
let name: String = findUser(1).unwrapOr("Unknown");
println("Found: " + name);
// Transformation with map
let upper: Option<String> = findUser(1).map(fnc(n: String) -> String { return n.toUpperCase(); });
println("Uppercase: " + upper.unwrapOr("–"));
// Chaining with andThen
let result: Option<Int64> = findUser(1)
.andThen(fnc(n: String) -> Option<Int64> {
if n.len() > 3 { return Option::some(n.len()); }
return Option::none();
});
println("Length: " + result.unwrapOr(0).toString());
// orElse fallback
let alternative: Option<String> = findUser(99)
.orElse(fnc() -> Option<String> { return Option::some("Guest"); });
println("Alternative: " + alternative.unwrap());
return 0;
}
}
result
import tinox.core.result;
The result module provides the generic type Result<T>, which represents either a success value (Ok) or an error message as a string (Err). It enables explicit, type-safe error handling without exceptions.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Result::ok | fn ok(value: T) -> Result<T> | Creates a successful result with the specified value. |
Result::err | fn err(error: String) -> Result<T> | Creates an error result with the specified error message. |
Result::isOk | fn isOk() -> Bool | Returns true if the result is successful. |
Result::isErr | fn isErr() -> Bool | Returns true if the result contains an error. |
Result::unwrap | fn unwrap() -> T | Returns the success value. Throws the error message if the result is an error. |
Result::unwrapOr | fn unwrapOr(defaultValue: T) -> T | Returns the success value or the specified default value if an error is present. |
Result::getError | fn getError() -> String | Returns the error message (empty string if no error). |
Result::map | fn map<U>(transform: fnc(T) -> U) -> Result<U> | Applies a transformation function to the success value. Errors are passed through unchanged. |
Result::andThen | fn andThen<U>(transform: fnc(T) -> Result<U>) -> Result<U> | Chains operations that themselves return a Result (flatmap). Errors are short-circuited. |
Result::mapErr | fn mapErr(transform: fnc(String) -> String) -> Result<T> | Transforms the error message. Success values are passed through unchanged. |
Example
import tinox.core.result;
class Main
{
fnc divide(a: Int64, b: Int64) -> Result<Int64>
{
if b == 0
{
return Result::err("Division by zero");
}
return Result::ok(a / b);
}
fnc main() -> Int32
{
// Simple usage
let r: Result<Int64> = divide(10, 2);
if r.isOk()
{
println("Result: " + r.unwrap().toString());
}
// Error case
let err: Result<Int64> = divide(5, 0);
println("Error: " + err.getError());
println("Fallback: " + err.unwrapOr(-1).toString());
// Chaining
let chain: Result<String> = divide(100, 4)
.map(fnc(n: Int64) -> String { return "Value: " + n.toString(); })
.mapErr(fnc(e: String) -> String { return "Calculation failed – " + e; });
println(chain.unwrapOr("No result"));
return 0;
}
}
logger
import tinox.core.logger;
The logger module provides a structured logging system with configurable log levels. Each logger has a name and outputs messages with a timestamp, level, and name. The LogLevel class defines the four levels Debug, Info, Warn, and Error.
Setting the environment variable TINOX_LOG_JSON=true makes every Logger (including instances injected via @Log) emit a JSON object with the fields timestamp, level, logger, and message instead of the plain-text line – handy for structured log pipelines. Without the variable (or with any value other than "true"), the previous plain-text format [timestamp] [LEVEL] [name] message is unchanged.
Classes & Methods
| Class / Method | Signature | Description |
|---|---|---|
Logger::new | fn new(name: String) -> Logger | Creates a new logger with the specified name. The default level is Info. |
Logger::debug | fn debug(logger: Logger, message: String) -> Nothing | Outputs a debug message if the level is <= Debug. |
Logger::info | fn info(logger: Logger, message: String) -> Nothing | Outputs an info message if the level is <= Info. |
Logger::warn | fn warn(logger: Logger, message: String) -> Nothing | Outputs a warning message if the level is <= Warn. |
Logger::error | fn error(logger: Logger, message: String) -> Nothing | Outputs an error message if the level is <= Error. |
Logger::setLevel | fn setLevel(logger: Logger, level: LogLevel) -> Nothing | Sets the minimum log level of the logger. Messages below the level are suppressed. |
Logger::log | fn log(logger: Logger, level: String, message: String) -> Nothing | Internal output method: writes a formatted line with timestamp, level, and logger name. |
LogLevel::Debug | fn Debug() -> LogLevel | Returns the log level Debug (value 0). |
LogLevel::Info | fn Info() -> LogLevel | Returns the log level Info (value 1). |
LogLevel::Warn | fn Warn() -> LogLevel | Returns the log level Warn (value 2). |
LogLevel::Error | fn Error() -> LogLevel | Returns the log level Error (value 3). |
Example
import tinox.core.logger;
// Create logger
let log: Logger = Logger::new("my-app");
// Default level is Info
Logger::info(log, "Application started.");
Logger::warn(log, "Configuration file not found – using defaults.");
Logger::error(log, "Database connection failed.");
// Debug messages are invisible by default
Logger::debug(log, "This text does not appear yet.");
// Set level to Debug
Logger::setLevel(log, LogLevel::Debug());
Logger::debug(log, "Debug mode active. All messages are output.");
// Module-specific logger
let dbLog: Logger = Logger::new("database");
Logger::setLevel(dbLog, LogLevel::Warn());
Logger::info(dbLog, "This info is suppressed.");
Logger::warn(dbLog, "Connection pool nearly exhausted.");
JSON Output
// Set before the process starts (or via Env::setVar):
// TINOX_LOG_JSON=true
let log: Logger = Logger::new("my-app");
Logger::info(log, "Application started.");
// {"timestamp":"...","level":"INFO","logger":"my-app","message":"Application started."}
ORM & Lambda Query DSL
Tinox provides a type-safe ORM inspired by JPA/QueryDSL. Classes are annotated as database tables, and all queries run through a type-safe lambda DSL — no string-based queries.
Annotations
| Annotation | Target | Description |
|---|---|---|
@Entity | Class | Marks the class as a database entity |
@Table("name") | Class | Sets the table name (default: lowercase class name) |
@Id | Field | Primary key |
@Column("col") | Field | Maps field to a DB column (default: field name) |
@GeneratedValue | Field | ID generated by the DB (AUTO_INCREMENT / SERIAL) |
@NotNull | Field | Column is NOT NULL |
Entity Definition
@Entity
@Table("users")
class User {
@Id @GeneratedValue
var id: Int64;
@Column("name")
var name: String;
@Column("age")
var age: Int64;
@Column("active")
var active: Bool;
}tinox.toml – [database] Configuration
# PostgreSQL
[database]
driver = "postgres"
url = "postgres://user:pass@localhost:5432/mydb"
pool = 10
# MySQL / MariaDB
[database]
driver = "mysql"
url = "mysql://user:pass@localhost:3306/mydb"
# SQLite (no DB server required)
[database]
driver = "sqlite"
url = "sqlite:///path/to/database.db"Lambda Query DSL
// All users over 18, sorted alphabetically, max 10 results
let adults: List<User> = DB.of(User)
.filter(u -> u.age > 18)
.orderBy(u -> u.name)
.limit(10)
.list();
// Read a single user
let alice: User = DB.of(User)
.filter(u -> u.name == "Alice")
.first();
// Count active users
let n: Int64 = DB.of(User)
.filter(u -> u.active == true)
.count();Lambda → SQL Translation Table
| Lambda Expression | SQL Fragment |
|---|---|
u.age > 18 | age > $1 |
u.name == "Alice" | name = $1 |
u.name != "Bob" | name != $1 |
u.name.startsWith("A") | name LIKE 'A%' |
u.name.endsWith("z") | name LIKE '%z' |
u.name.contains("oo") | name LIKE '%oo%' |
expr1 && expr2 | (pred1) AND (pred2) |
expr1 || expr2 | (pred1) OR (pred2) |
!expr | NOT (pred) |
Save, Update, Delete
// INSERT (ID assigned by the DB)
let user: User = User {};
user.name = "Alice";
user.age = 30;
let saved: User = DB.of(User).save(user);
// DELETE
DB.of(User).delete(saved);
// Save multiple entities
let list: List<User> = List::new();
list.add(user1);
list.add(user2);
DB.of(User).saveAll(list);Error Handling
Database errors are currently logged to stderr. Integration with try/catch is planned for a future release. Connection errors at startup terminate the program with a descriptive message.