Skip to main content

daslang

daslang (formerly daScript) language reference - gen2 syntax, type system, memory model, generics, macros, standard library. Invoke whenever a task involves writing, reviewing, refactoring, debugging, or understanding .das files or a daslang project.

Source facts

Repository
GaijinEntertainment/daScript
Last source activity
October 15, 2026 at 08:01
Detected SKILL.md language
English
Stars
1,191
Forks
126

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
16 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
daslang
description
daslang (formerly daScript) language reference - gen2 syntax, type system, memory model, generics, macros, standard library. Invoke whenever a task involves writing, reviewing, refactoring, debugging, or understanding .das files or a daslang project.
# daslang Statically typed scripting language for games and real-time applications, by Gaijin Entertainment. `.das` files run interpreted, AOT-compiled to C++, or LLVM-JIT-compiled; data layout mirrors C++. **gen2 is the DEFAULT parser:** a `.das` file is gen2 (C-like, braces) unless it opts out with `options gen2 = false` - never infer gen1 (indentation-based) from a missing `options gen2` marker. Never write gen1; by convention new files still open with `options gen2`. Everything below is gen2. ## References Detailed semantics live in `./references/`: - `types.md` - type catalog, vector lattice, literal suffixes, enums/variants/tuples/ bitfields, fixed arrays, the two const positions, temporary (`#`) and distinct types - `functions.md` - parameters, named arguments, overload resolution, operator overloading, computed properties, pipes, precedence - `structs-and-classes.md` - initialization modes, virtuals, sealed/static, runtime type checks - `closures.md` - blocks/lambdas/function pointers, capture modes, generators, iterators - `memory.md` - the const model, move/copy/clone, finalizers, `inscope`, delete semantics, contexts and threading, unsafe catalog - `generics.md` - `auto(TT)`, type contracts, typeinfo traits, `static_if`, pattern matching - `macros.md` - compilation pipeline, macro classes, `qmacro`, `qmatch`, annotations - `modules-and-stdlib.md` - module declarations, `require`, `with (module ...)`, options, containers, daslib catalog - `strings.md` - the string surface, byte views, the parse family, `build_string`, conversions, regular expressions - `files-and-paths.md` - fio helpers, directory walking, subprocess capture, glob patterns - `json.md` - `sprint_json`/`sscan_json`, `JV`, `JsonValue?`, safe navigation - `queries.md` - comprehensions, the linq surface, what fuses into one pass - `cli-and-config.md` - argv parsing, environment twins, config knobs - `everything.md` - generated digest of every module: what it is for, every public symbol, each with a one-line description where the source carries one; search it by symbol or intent before writing a helper, and read it whole only when auditing a file set for duplicates ## Functions ```das def add(a, b : int) : int { // `a, b : int` - one type for both names return a + b } def twice(a) => a + a // arrow body; untyped param makes it generic ``` - Typed parameter groups separate with `,` or `;`. - A parameter is const unless declared `var a : int`. - Return type follows `:`, inferred when omitted; a `=>` body's expression must start on the same line as the `=>`. - Named arguments follow positionals: `foo(pos, name = value)`. - Defaults: `def greet(name : string = "world")`. - Visibility is a prefix keyword - `def private helper()`; there is no `[private]` annotation. ## Variables and the const model ```das let a = 13 // immutable handle: type is `int const` var b = 13 // mutable: type is `int` var c : float = 0.5 ``` `let x = e` means `x : auto const = e` - const lives entirely in the type, and dereference, indexing and field access flow it onto the result, so a `let` handle gives const access to *everything reachable through it*. Never strip const with `reinterpret` to write: the optimizer trusts the const type and may silently delete the write. ## Control flow ```das if (x > 0) { r = 1 } elif (x < 0) { r = -1 } else { r = 0 } for (i in range(10)) { ... } // 0..9 for (x in arr) { ... } // arrays, tables, iterators while (running) { ... } ``` - Conditions must be `bool` - no truthiness: `if (ptr != null)`. `if` / `for` / `while` / `static_if` all require the parentheses. - Postfix conditionals: `return x if (cond)`, likewise `break` / `continue`. - A bare `{ ... }` at statement level is a lexical scope, and takes `{ ... } finally { ... }`. - `for (k, v in keys(tab), values(tab))` iterates in parallel. ## Strings - Strings are immutable; interpolate `{expr}` as in `"hello, {name}"` - not `${}` (compiles, prints a literal `$`), not `%s` - with format specifiers `"{value:08x}"`, escaping literal braces `"literal \{braces\}"`. Number to string: `"{x}"` or `string(42)`. - `length(s)` / `empty(s)` need no `require`; the rest of the string API needs `require strings`. - `int("123")` does NOT exist; `to_int` (`require strings`) silently returns `0` on garbage. External input goes through `try_to_int` / `try_to_float` from `require daslib/strings_convert`, whose Result distinguishes invalid input from a real zero. ## Pipes and block arguments ```das foo |> setXY(10, 11) // setXY(foo, 10, 11) arr |> sort() $(a, b) => a < b // trailing block pipes as last argument build_string() $(var writer) { // block with parameters writer |> write("hello") } ``` - A block/lambda immediately after a call is piped as its LAST argument ("assumed pipe"); a parameterless block needs no `$`: `defer() { cleanup() }`. - Arrow shorthand also spells lambdas: `@(x) => x + 1` (capturing), `@@(x) => x + 1` (no-capture function pointer). - `a.foo(b)` is sugar for `foo(a, b)` only when `a` is a struct/class value; primitives and lambda-typedef values need `|>`. ## Collections ```das var a <- [1, 2, 3] // array<int> literal (heap) let f = fixed_array(1, 2, 3) // fixed-size int[3] (stack) var t <- { "hp" => 100, "mp" => 50 } // table<string; int> literal var s : table<string> // one type param = a SET let v = t?["hp"] ?? 0 // safe lookup with default var sq <- [for (x in range(10)); x * x] // array comprehension var m <- { for (x in range(5)); x => x * x } // table comprehension ``` - `table[key]` on a mutable table INSERTS a default entry when missing - read with `t?[key] ?? default`, test with `key_exists(t, k)`. - Never do two `[]` lookups on the same table in one expression - rehashing can invalidate the first reference. - `push` copies, `emplace` moves (zeroes the source), `push_clone` deep-clones. Bulk forms `push_from` / `push_clone_from` take a whole array at once. ## Move, copy, clone | Operator | Effect | |---|---| | `=` | copy - value types (`int`, `float`, `bool`, `string`, pointers, POD structs) | | `<-` | move - transfers ownership, source is zeroed | | `:=` | clone - deep copy, source unchanged | ```das def make() : array<int> { var r <- [1, 2, 3] return <- r // moving return for non-copyable types } ``` Structs are copyable only if all fields are. `delete` on a lambda requires `unsafe` - copies alias one shared capture frame. ## Pointers ```das var p = new Foo(x = 1) // heap allocation, type Foo? let x = p?.x ?? 0 // safe navigation + null coalescing var inscope q = new Foo() // auto-deleted at scope exit unsafe { delete p // manual delete is unsafe } ``` - `T?` is a nullable pointer with TWO const positions: `Foo const?` = const pointee, `Foo? const` = const pointer. Writing through a parameter needs both non-const: `var p : Foo?`. - `addr(x)` requires `unsafe`; `safe_addr(x)` yields a temporary pointer (`T?#`) without it. ## Enums, variants, tuples, bitfields ```das enum Color { red green } let i = int(Color.red) // dot access; explicit cast to int var t = (1, 2.0, "three") let first = t._0 // access by index let (x, y, z) = t // destructuring bitfield Flags { read write } var fl : Flags = Flags.read | Flags.write fl.write = false // single-bit assignment if (fl.read) { ... } // single-bit test ``` Without the explicit `: Flags`, `var fl = Flags.read | Flags.write` infers an ANONYMOUS bitfield type and the named single-bit access (`fl.read`) stops compiling. Variants (tagged unions) are declared like structs, constructed with exactly one field (`Value(asInt = 42)`), tested with `is`, read with `as` / `?as ... ?? default`. Writing a variant field or switching the active field requires `unsafe`. A case declared without a type (`variant Maybe { nothing; value : int }`) carries no value: `Maybe(nothing)` constructs it, `is` tests it, and `as`, `?as` and field reads do not compile on it. ## Memory model Locals and structs live on the stack; `new`, arrays and tables use the current context's heap. Cleanup is deterministic and explicit - `delete`, `var inscope`, or moving ownership out with `<-`; a plain local `var arr : array<int>` is NOT finalized at scope exit. Each context (thread) owns its heap and cannot retain pointers into another's - clone what crosses. `delete` on a container of pointers finalizes AND FREES every pointee; `clear()` first when the pointers are non-owning. ## Error handling - `panic("msg")`, and any runtime error (a failed bounds check, an error raised by a bound C++ function), unwinds to the nearest enclosing `try { } recover { }`; the `recover` block runs and execution continues after it, in every execution tier. `recover` binds nothing - there is no exception value to match on; the message is `this_context().last_exception` (`require daslib/rtti`), with a trailing newline. Under the JIT the pair compiles to a builtin call, and a `return` inside the `try` block is a compile error there (the interpreter allows it). With no enclosing `try` the error unwinds out of the script: the daslang CLI prints it and exits nonzero, an embedding host gets it back as a context error. Never design APIs around panic-as-control-flow. - `assert(cond, "msg")` may be stripped in release; `verify(cond, "msg")` always runs. The message must be a string CONSTANT - for a runtime-value diagnostic write `if (!cond) panic("bad n = {n}")`. An asserted expression with side effects (invoking a lambda counts) is rejected; use `verify`. - Recoverable errors are values: return `bool`, `T?`, or a variant with an error case. - `{ } finally { }` is SKIPPED on panic, by design - no must-run-on-failure cleanup there. ## Callable types | Kind | Prefix | Allocation | Captures | Storable | Copyable | |---|---|---|---|---|---| | block | `$` | stack | surrounding scope by reference | no | no | | lambda | `@` | heap | explicit modes: copy/move/clone/ref | yes | yes - pointer copy (alias) | | function | `@@` | none | none | yes | yes | `@@name` is a pointer to a named function. ## Gotchas - **No implicit conversions between values.** `float_var + int_var` is a compile error; cast one side. Bare integer LITERALS are the one exception, adapting to a known numeric target - `f + 1`, `d > 1`, `var f : float = 1`, `return 200` into `uint8` - but NOT at call arguments (`take_f(1)` fails when `take_f` wants float) or parameter defaults, and float literals never adapt to double. No `bool(int)` - write `x != 0`. No `string(bool)` - interpolate `"{flag}"`. - **Hex literals are `uint`** - `int(0x3F)` when an int is needed. - **Reserved words that look like identifiers:** `where`, `shared`, `label`, `expect`, `pass`, `explicit`, `capture`, `deref`, `template` are keywords; `range`, `urange`, `range64`, `urange64`, `block`, `function`, `lambda`, `iterator`, and the small-vector type names (`half2`, `short4`, `byte16`, ...) are type tokens. Using any as a variable/parameter/field/function or annotation-argument name is a syntax error - including `@range = 5` on a field. - **A statement-level expression must fit one line** unless wrapped in `(...)`: a continuation line starting with `+` or `-` parses as a separate unary statement and is silently optimized away. - **`options stack = N` counts only in the MAIN module** - a required library cannot raise the program's stack. - **`require` paths use `/`**, resolve module mounts or same-directory names, and cannot contain hyphens; a file elsewhere in the tree needs a relative path with the explicit `.das` extension: `require ../lib/util.das`. - **`with (self) { x = 1 }` brings struct fields into scope**; `assume alias = long.chain` makes a textual alias (re-evaluated each use, no copy). --- Verified against daslang 0.6.4 (2026-08-08). Maintained in the daslang repository under `skills/daslang/`; report errors there.
View on GitHub