diff --git a/doc/source/reference/tutorials.rst b/doc/source/reference/tutorials.rst index f5ec6606f..dd978c5da 100644 --- a/doc/source/reference/tutorials.rst +++ b/doc/source/reference/tutorials.rst @@ -85,6 +85,8 @@ introduced in earlier tutorials. tutorials/54_glob.rst tutorials/55_linq_decs.rst tutorials/56_linq_query.rst + tutorials/57_toml.rst + tutorials/58_logger.rst .. _tutorials_building_from_sdk: @@ -206,6 +208,7 @@ Run any tutorial from the project root:: tutorials/macros/16_template_type_macro.rst tutorials/macros/17_qmacro.rst tutorials/macros/18_with_boost.rst + tutorials/macros/19_add_module_option.rst .. _tutorials_dashv: diff --git a/doc/source/reference/tutorials/03_operators.rst b/doc/source/reference/tutorials/03_operators.rst index 5d617eeaf..d1801f376 100644 --- a/doc/source/reference/tutorials/03_operators.rst +++ b/doc/source/reference/tutorials/03_operators.rst @@ -86,12 +86,12 @@ Copy vs move — introduction ``=`` is copy assignment — works for simple types and copyable values. ``<-`` is move assignment — transfers ownership and zeroes the source:: - var p = 10 + let p = 10 var q = p // q is a copy of p q = 99 // p is unchanged var s = 42 - var r <- s // r gets 42, s is zeroed + let r <- s // r gets 42, s is zeroed Move matters for arrays, tables, lambdas, and other heap-allocated types. See :ref:`tutorial_arrays` and :ref:`Move, copy, clone `. diff --git a/doc/source/reference/tutorials/17_move_copy_clone.rst b/doc/source/reference/tutorials/17_move_copy_clone.rst index e0d81321a..857553264 100644 --- a/doc/source/reference/tutorials/17_move_copy_clone.rst +++ b/doc/source/reference/tutorials/17_move_copy_clone.rst @@ -81,7 +81,7 @@ Type compatibility - Yes - Yes * - lambda - - No + - Yes (alias) - Yes - No * - iterator @@ -93,6 +93,12 @@ Type compatibility - No - No +.. note:: + + ``=`` on a lambda copies the fat pointer — both variables share the same + capture frame (aliasing, not an independent copy). See + :ref:`tutorial_lambdas` for details. + Relaxed assign ============== diff --git a/doc/source/reference/tutorials/21_error_handling.rst b/doc/source/reference/tutorials/21_error_handling.rst index c47973de6..66eaaa5fd 100644 --- a/doc/source/reference/tutorials/21_error_handling.rst +++ b/doc/source/reference/tutorials/21_error_handling.rst @@ -40,8 +40,12 @@ general exception handling — only panics are caught:: .. note:: - You cannot ``return`` from inside ``try`` or ``recover`` blocks. - Assign results to a variable instead. + Execution resumes after the ``try``/``recover`` block, but a panic means + the program reached a broken state. Use ``recover`` to capture the failure + (log it, report it) before deciding to stop — **not** as resumable + exception handling and not for soft, expected errors. For those, return a + status value instead — see :ref:`tutorial_option_and_result`. (Consistent + with this, a ``finally`` block is skipped when its body panics.) assert and verify ================= diff --git a/doc/source/reference/tutorials/23_string_format.rst b/doc/source/reference/tutorials/23_string_format.rst index 9e63a7974..f11106b09 100644 --- a/doc/source/reference/tutorials/23_string_format.rst +++ b/doc/source/reference/tutorials/23_string_format.rst @@ -24,8 +24,8 @@ Inside ``{expr}`` interpolation, add ``:flags width.precision type``:: **Flags:** - ``0`` — zero-pad -- ``-`` — left-align - ``+`` — force sign +- ``<`` — left-align (e.g. ``{x:<10d}``) **Type characters:** diff --git a/doc/source/reference/tutorials/32_operator_overloading.rst b/doc/source/reference/tutorials/32_operator_overloading.rst index 5ebde82be..ae533c24b 100644 --- a/doc/source/reference/tutorials/32_operator_overloading.rst +++ b/doc/source/reference/tutorials/32_operator_overloading.rst @@ -24,32 +24,9 @@ Overload ``+``, ``-``, ``*``, ``/``, ``%`` to give your types arithmetic behaviour. Each operator is a binary function that takes two values and returns a result: -.. code-block:: das - - struct Vec2 { - x : float - y : float - } - - def operator +(a, b : Vec2) : Vec2 { - return Vec2(x = a.x + b.x, y = a.y + b.y) - } - - def operator -(a, b : Vec2) : Vec2 { - return Vec2(x = a.x - b.x, y = a.y - b.y) - } - - def operator *(a : Vec2; s : float) : Vec2 { - return Vec2(x = a.x * s, y = a.y * s) - } - - def operator *(s : float; a : Vec2) : Vec2 { - return a * s - } - - def operator /(a : Vec2; s : float) : Vec2 { - return Vec2(x = a.x / s, y = a.y / s) - } +.. literalinclude:: ../../../../tutorials/language/32_operator_overloading.das + :language: das + :lines: 27-50 Usage:: @@ -122,22 +99,9 @@ Overload ``+=``, ``-=``, ``*=``, ``/=``, ``%=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=`` and others for in-place modification. The first argument must be ``var ... &`` (mutable reference): -.. code-block:: das - - def operator +=(var a : Vec2&; b : Vec2) { - a.x += b.x - a.y += b.y - } - - def operator -=(var a : Vec2&; b : Vec2) { - a.x -= b.x - a.y -= b.y - } - - def operator *=(var a : Vec2&; s : float) { - a.x *= s - a.y *= s - } +.. literalinclude:: ../../../../tutorials/language/32_operator_overloading.das + :language: das + :lines: 145-158 Usage:: diff --git a/doc/source/reference/tutorials/44_compile_and_run.rst b/doc/source/reference/tutorials/44_compile_and_run.rst index c27c90555..68fc3662f 100644 --- a/doc/source/reference/tutorials/44_compile_and_run.rst +++ b/doc/source/reference/tutorials/44_compile_and_run.rst @@ -24,7 +24,7 @@ section. options gen2 options multiple_contexts // required when holding smart_ptr - require daslib/rtti + require daslib/ast require daslib/debugger require daslib/jobque_boost diff --git a/doc/source/reference/tutorials/46_apply_in_context.rst b/doc/source/reference/tutorials/46_apply_in_context.rst index 92f76bbc8..d0d39603c 100644 --- a/doc/source/reference/tutorials/46_apply_in_context.rst +++ b/doc/source/reference/tutorials/46_apply_in_context.rst @@ -77,15 +77,17 @@ From the caller's perspective, these look like normal functions: init_counter_service() print(" increment() = {increment()}\n") print(" increment() = {increment()}\n") + print(" increment() = {increment()}\n") print(" get_counter() = {get_counter()}\n") add_to_counter(10) - print(" after add = {get_counter()}\n") + print(" after add_to_counter(10) = {get_counter()}\n") print(" local counter = {counter}\n") // output: // increment() = 1 // increment() = 2 - // get_counter() = 2 - // after add = 12 + // increment() = 3 + // get_counter() = 3 + // after add_to_counter(10) = 13 // local counter = 0 The agent context's ``counter`` is modified — the caller's local diff --git a/doc/source/reference/tutorials/47_data_walker.rst b/doc/source/reference/tutorials/47_data_walker.rst index 8a221a987..8daf4693b 100644 --- a/doc/source/reference/tutorials/47_data_walker.rst +++ b/doc/source/reference/tutorials/47_data_walker.rst @@ -35,22 +35,9 @@ A ``DapiDataWalker`` subclass overrides only the callbacks you need. All 87 methods default to no-ops, so a minimal walker that prints integers, floats, strings, and booleans is very small: -.. code-block:: das - - class ScalarPrinter : DapiDataWalker { - def override Int(var value : int&) : void { - print(" int: {value}\n") - } - def override Float(var value : float&) : void { - print(" float: {value}\n") - } - def override String(var value : string&) : void { - print(" string: \"{value}\"\n") - } - def override Bool(var value : bool&) : void { - print(" bool: {value}\n") - } - } +.. literalinclude:: ../../../../tutorials/language/47_data_walker.das + :language: das + :lines: 41-54 To walk a value, create the walker, wrap it with ``make_data_walker``, then call ``walk_data`` with a pointer and ``TypeInfo``: @@ -375,21 +362,9 @@ Mutating data in-place Scalar callbacks receive ``var value : T&`` — a mutable reference. This means the walker can modify data during traversal: -.. code-block:: das - - class FloatClamper : DapiDataWalker { - lo : float = 0.0 - hi : float = 1.0 - - def override Float(var value : float&) : void { - if (value < lo) { - value = lo - } - if (value > hi) { - value = hi - } - } - } +.. literalinclude:: ../../../../tutorials/language/47_data_walker.das + :language: das + :lines: 767-779 Walking a ``Particle`` with out-of-range floats clamps them to ``[0..1]``: diff --git a/doc/source/reference/tutorials/50_soa.rst b/doc/source/reference/tutorials/50_soa.rst index 43fac2c45..a596b4ed2 100644 --- a/doc/source/reference/tutorials/50_soa.rst +++ b/doc/source/reference/tutorials/50_soa.rst @@ -22,9 +22,9 @@ Prerequisites: familiarity with structs and arrays. .. code-block:: das options gen2 - options no_unused_function_arguments = false require daslib/soa + require math What is SOA? diff --git a/doc/source/reference/tutorials/51_delegate.rst b/doc/source/reference/tutorials/51_delegate.rst index 7159fb067..32365752e 100644 --- a/doc/source/reference/tutorials/51_delegate.rst +++ b/doc/source/reference/tutorials/51_delegate.rst @@ -20,9 +20,9 @@ Prerequisites: familiarity with lambdas, function pointers, and typedefs. .. code-block:: das options gen2 - options no_unused_function_arguments = false require daslib/delegate + require math Declaring delegate types diff --git a/doc/source/reference/tutorials/56_linq_query.rst b/doc/source/reference/tutorials/56_linq_query.rst index 773723465..436c2f904 100644 --- a/doc/source/reference/tutorials/56_linq_query.rst +++ b/doc/source/reference/tutorials/56_linq_query.rst @@ -152,3 +152,5 @@ exact scope. Full source: :download:`tutorials/language/56_linq_query.das <../../../../tutorials/language/56_linq_query.das>` Previous tutorial: :ref:`tutorial_linq_decs` + + Next tutorial: :ref:`TOML ` diff --git a/doc/source/reference/tutorials/57_toml.rst b/doc/source/reference/tutorials/57_toml.rst new file mode 100644 index 000000000..68eada5fb --- /dev/null +++ b/doc/source/reference/tutorials/57_toml.rst @@ -0,0 +1,169 @@ +.. _tutorial_toml: + +======================== +TOML +======================== + +.. index:: + single: Tutorial; TOML + single: Tutorial; TOML Parsing + single: Tutorial; read_toml + single: Tutorial; toml + +This tutorial covers ``daslib/toml`` — parsing `TOML 1.0 +`_ configuration files in daslang. + +``read_toml`` decodes TOML into the **same** ``JsonValue?`` tree that +``daslib/json`` produces, so every ``json_boost`` accessor (``?.``, ``?[]``, +``??``, ``from_JV``) works on a parsed TOML document with no extra code. + +Parsing +======= + +``read_toml`` takes a string and an out error string, and returns +``JsonValue?``. A TOML document is always a table, so a successful parse is +always an ``_object``. On malformed input it returns ``null`` and fills the +error — the same fail-fast contract as ``read_json``:: + + var error : string + var doc = read_toml("title = \"daslang\" + version = 3", error) + if (doc != null) { + print("title: {doc?.title ?? "?"}\n") // daslang + print("version: {doc?.version ?? 0}\n") // 3 + } + +Type mapping +============ + +Each TOML value maps to one ``JsValue`` case: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - TOML + - ``JsValue`` + * - table / inline table + - ``_object`` (``table``) + * - array / array-of-tables + - ``_array`` (``array``) + * - string (all four forms, already unescaped) + - ``_string`` + * - integer (decimal / hex / octal / binary) + - ``_longint`` (``int64``) + * - float (incl. ``inf`` / ``nan``) + - ``_number`` (``double``) + * - boolean + - ``_bool`` + * - date-time / local-date / local-time + - ``_string`` (RFC-3339 lexical form preserved) + +JSON has no native date type, so TOML date-times are kept verbatim as +strings — you get back the exact text the document used. + +Nested tables and dotted keys +============================= + +``[section]`` headers and dotted keys both build nested ``_object`` tables. +Reach into them with chained ``?.`` or ``?[]``, exactly like JSON:: + + var doc = read_toml("[server] + host = \"localhost\" + port = 8080 + + [server.tls] + enabled = true", error) + + print(doc?.server?.host ?? "?") // localhost + print(doc?["server"]?["port"] ?? 0) // 8080 + print(doc?.server?.tls?.enabled ?? false)// true + print(doc?.server?.timeout ?? -1) // -1 (missing path -> default) + +Arrays and arrays-of-tables +=========================== + +A bare array becomes an ``_array`` of scalars. A repeated ``[[name]]`` +header builds an ``_array`` of ``_object`` tables — one element per block:: + + var doc = read_toml("ports = [8000, 8001, 8002] + + [[user]] + name = \"alice\" + + [[user]] + name = \"bob\"", error) + + print(doc?.ports?[1] ?? 0) // 8001 + print(doc?.user?[0]?.name ?? "?") // alice + + // Walk the array of tables + let users = doc?.user + if (users != null && users is _array) { + for (u in users as _array) { + print("{u?.name ?? "?"}\n") + } + } + +Inline tables +============= + +``{ k = v, ... }`` on one line is an inline table — also an ``_object``:: + + var doc = read_toml("point = \{ x = 1, y = 2 \}", error) + print(doc?.point?.x ?? 0) // 1 + +Integers and floats +=================== + +TOML accepts decimal, ``0x`` hex, ``0o`` octal and ``0b`` binary integers +(plus ``_`` digit separators), and floats with exponents, ``inf`` and +``nan``. All integers decode to ``_longint``, all floats to ``_number``:: + + var doc = read_toml("hex = 0xFF + oct = 0o755 + big = 6.022e23 + huge = inf", error) + + print(doc?.hex ?? 0) // 255 + print(doc?.oct ?? 0) // 493 + +Error handling +============== + +``read_toml`` is fail-fast — malformed input returns ``null`` with a +descriptive message in the error out-param:: + + var doc = read_toml("key = = broken", error) + print(doc == null) // true + print(!empty(error)) // true + +Deserializing into a struct +=========================== + +Because the result is the same tree as JSON, ``from_JV`` fills a struct by +compile-time reflection — turning a config file into typed data:: + + struct ServerConfig { + host : string + port : int + workers : int + } + + var doc = read_toml("host = \"0.0.0.0\" + port = 9090 + workers = 4", error) + let cfg = from_JV(doc, type) + // cfg.host = "0.0.0.0", cfg.port = 9090, cfg.workers = 4 + +.. seealso:: + + Full source: :download:`tutorials/language/57_toml.das <../../../../tutorials/language/57_toml.das>` + + Previous tutorial: :ref:`LINQ query syntax `. + + Next tutorial: :ref:`Logger `. + + :ref:`JSON tutorial ` — the ``JsonValue?`` tree and ``json_boost`` accessors used here. + + :doc:`/stdlib/generated/toml` — TOML module reference. diff --git a/doc/source/reference/tutorials/58_logger.rst b/doc/source/reference/tutorials/58_logger.rst new file mode 100644 index 000000000..f97e28233 --- /dev/null +++ b/doc/source/reference/tutorials/58_logger.rst @@ -0,0 +1,110 @@ +.. _tutorial_logger: + +======================== +Logger +======================== + +.. index:: + single: Tutorial; Logger + single: Tutorial; logging + single: Tutorial; logger_info + single: Tutorial; JSON Lines + +This tutorial covers ``daslib/logger`` — structured logging that writes one +JSON object per line ("JSON Lines" / ``ndjson``). Each record carries an +ISO-8601 UTC timestamp, a level, a free-form dotted category, the message, +and an optional ``fields`` object. + +The module is built for tool back-ends — MCP servers, language servers — +where stdout is a wire protocol and a stray ``print()`` would corrupt the +stream. All public names carry the ``logger_`` prefix so short verbs like +``info`` / ``close`` don't collide with user code. + +Basic logging +============= + +Point the logger at a file with ``logger_set_path`` (or +``logger_set_name``, which writes to ``{get_das_root()}/logs/.log``), +then call the level helpers. Each call is ``(category, message)``:: + + logger_set_path("app.log") + logger_info("app", "starting up") + logger_warning("app.net", "connection retry") + logger_error("app.db", "query failed") + +Each line is a complete JSON object:: + + {"ts":"2026-06-13T04:30:36.600Z","level":"info","cat":"app","msg":"starting up"} + +Structured fields +================= + +Pass a ``JsonValue?`` as the optional fourth argument to attach +machine-readable context. The ``json_boost`` named-tuple form +``JV((k=v, ...))`` is the shortest way to build it:: + + logger_info("app.req", "handled request", + JV((method = "GET", path = "/users", status = 200, ms = 12))) + + // {"ts":"...","level":"info","cat":"app.req","msg":"handled request", + // "fields":{"method":"GET","path":"/users","status":200,"ms":12}} + +Level filtering +=============== + +Records below the minimum level are dropped before formatting. The default +minimum is ``LOG_INFO`` (so ``logger_debug`` is silent unless you lower the +bar). Raise it and info-level records disappear:: + + logger_set_min_level(LOG_WARNING) + logger_info("app", "dropped — below the bar") + logger_warning("app", "kept") + +The runtime levels are ``LOG_TRACE``, ``LOG_DEBUG``, ``LOG_INFO``, +``LOG_WARNING``, ``LOG_ERROR``, ``LOG_CRITICAL``. + +Per-category overrides +====================== + +``logger_set_category_level`` lifts (or lowers) the bar for one category and +everything beneath it in the dotted hierarchy. An override on ``"app.trace"`` +also governs ``"app.trace.sql"``, ``"app.trace.http"``, etc. Anything outside +that prefix still uses the global minimum:: + + logger_set_category_level("app.trace", LOG_DEBUG) + logger_debug("app.trace.sql", "kept — under the app.trace override") + logger_debug("app.other", "dropped — global minimum is still INFO") + logger_clear_category_levels() + +The diversion hook +================== + +``logger_install_hook`` plugs a global debug agent into the runtime so that +``print()`` and ``to_log()`` — from this context or any future thread — are +redirected into the log file instead of stdout/stderr. This is the whole +point of the module for stdio-transport servers:: + + logger_set_min_level(LOG_DEBUG) // print() arrives as a DEBUG record + logger_install_hook() + print("this print is now diverted into the log file\n") + to_log(LOG_WARNING, "this to_log is diverted too\n") + +``print()`` reaches the hook as a **debug**-level record, so lower the +minimum to ``LOG_DEBUG`` first or the default ``LOG_INFO`` bar drops it. +``to_log`` carries whatever level you pass. + +Once the hook is installed, ``print()`` no longer reaches the terminal — to +display anything you must write straight to the stdout ``FILE`` with +``fwrite(fstdout(), ...)``, which bypasses the hook. ``logger_init(name)`` is +the one-line convenience that combines ``logger_set_name`` and +``logger_install_hook``. + +.. seealso:: + + Full source: :download:`tutorials/language/58_logger.das <../../../../tutorials/language/58_logger.das>` + + Previous tutorial: :ref:`TOML `. + + :ref:`JSON tutorial ` — the ``JV`` constructors used for the fields object. + + :doc:`/stdlib/generated/logger` — logger module reference. diff --git a/doc/source/reference/tutorials/daStrudel_04_time_manipulation.rst b/doc/source/reference/tutorials/daStrudel_04_time_manipulation.rst index ecc37917c..0d6cce074 100644 --- a/doc/source/reference/tutorials/daStrudel_04_time_manipulation.rst +++ b/doc/source/reference/tutorials/daStrudel_04_time_manipulation.rst @@ -39,11 +39,11 @@ Part A: ``fast(N)`` .. code-block:: das - let pat <- s("c4 e4 g4 c5") |> fast(2.0lf) + let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.4) |> fast(2.0lf) play(pat, 4.0) -The four-note arpeggio plays twice per cycle. ``N`` is a ``double``, -so non-integer values work — ``fast(1.5lf)`` gives 3 repetitions every +The four-note arpeggio plays twice per cycle. ``N`` accepts ``int``, +``float``, or ``double`` — ``fast(1.5lf)`` gives 3 repetitions every 2 cycles, useful for polyrhythms (covered in tutorial 05). Part B: ``slow(N)`` @@ -89,7 +89,7 @@ up by ``log2(N)`` octaves: .. code-block:: das - let pat <- s("c3 e3 g3 c4") |> hurry(2.0lf) + let pat <- note("c3 e3 g3 c4", "sine") |> sustain(0.4) |> hurry(2.0lf) play(pat, 4.0) The pattern plays twice as fast and an octave higher. This is a single @@ -105,10 +105,10 @@ operator like any other transform: .. code-block:: das - let pat <- note("c4 e4 g4 b4", "sine") + let pat <- (note("c4 e4 g4 b4", "sine") |> sustain(0.4) |> rev - |> slow(2.0lf) + |> slow(2.0lf)) play(pat, 6.0) Order matters — ``rev |> slow(2)`` reverses then stretches; ``slow(2) |> diff --git a/doc/source/reference/tutorials/daStrudel_05_euclidean_rhythms.rst b/doc/source/reference/tutorials/daStrudel_05_euclidean_rhythms.rst index db027fa55..19708ce47 100644 --- a/doc/source/reference/tutorials/daStrudel_05_euclidean_rhythms.rst +++ b/doc/source/reference/tutorials/daStrudel_05_euclidean_rhythms.rst @@ -92,12 +92,10 @@ with different ``k`` and you have a polyrhythm: .. code-block:: das - var kick <- atom("bd") |> euclid(3, 8) - var hat <- atom("hh") |> euclid(5, 8) - var layers : array - layers |> emplace <| kick - layers |> emplace <| hat - let pat <- stack(layers) + let pat <- stack([ + atom("bd") |> euclid(3, 8), + atom("hh") |> euclid(5, 8) + ]) play(pat, 6.0) Both layers complete every cycle (``n = 8`` in both), but the **3 @@ -106,11 +104,6 @@ grid positions, so the perceived feel is polyrhythmic. Use mismatched ``n`` values (e.g. ``(3, 8)`` against ``(2, 5)``) and the cycle length becomes ``lcm(8, 5) = 40`` — the patterns realign every 40 steps. -The ``var`` (not ``let``) on ``kick`` and ``hat`` matters: ``stack`` -takes ``array`` and ``emplace`` needs to **move** the lambda -(Patterns are non-copyable lambdas) — that requires a mutable -binding. - Part E: ``euclidRot(pat, k, n, rot)`` — rotate the onsets ========================================================= diff --git a/doc/source/reference/tutorials/daStrudel_12_synthesis.rst b/doc/source/reference/tutorials/daStrudel_12_synthesis.rst index 747b7df38..a8b17eaf7 100644 --- a/doc/source/reference/tutorials/daStrudel_12_synthesis.rst +++ b/doc/source/reference/tutorials/daStrudel_12_synthesis.rst @@ -38,7 +38,7 @@ Tonal character: Part B: Supersaw ================ -``supersaw`` is seven detuned saws stacked into one voice — the classic +``supersaw`` is five detuned saws stacked into one voice — the classic "trance" pad sound. It is much wider and fatter than a single saw; a low-pass filter tames the top end: diff --git a/doc/source/reference/tutorials/daStrudel_16_live_reloading.rst b/doc/source/reference/tutorials/daStrudel_16_live_reloading.rst index 0fad68997..b73ad745d 100644 --- a/doc/source/reference/tutorials/daStrudel_16_live_reloading.rst +++ b/doc/source/reference/tutorials/daStrudel_16_live_reloading.rst @@ -46,8 +46,9 @@ Part B: Preserving Player State ``require strudel/strudel_live`` is all it takes to keep the strudel player state alive across reloads. The module registers ``[before_reload]`` and ``[after_reload]`` hooks that serialise the -player (tracks, SID, BPM, timing) and the loaded SF2 data into the -persistent byte store: +player state (wall time, CPS/BPM, SID, and the sample bank) and the +loaded SF2 data into the persistent byte store. Tracks are not +serialised — ``strudel_init`` re-adds them on every reload: .. code-block:: das diff --git a/doc/source/reference/tutorials/daStrudel_17_hrtf_position.rst b/doc/source/reference/tutorials/daStrudel_17_hrtf_position.rst index 37301f1f5..536e62b18 100644 --- a/doc/source/reference/tutorials/daStrudel_17_hrtf_position.rst +++ b/doc/source/reference/tutorials/daStrudel_17_hrtf_position.rst @@ -74,7 +74,7 @@ Part B: Animated azimuth The setters accept patterns. Each event samples the modulation pattern at its onset to get its azimuth. With ``sine() |> range(-180, 180) |> slow(4)``, the source orbits the -listener once every 8 cycles (on a 0.5 cps stream): +listener once every 4 cycles (on a 0.5 cps stream): .. code-block:: das diff --git a/doc/source/reference/tutorials/dasAudio_04_spatial_audio.rst b/doc/source/reference/tutorials/dasAudio_04_spatial_audio.rst index 2e4fe6452..8980cc93d 100644 --- a/doc/source/reference/tutorials/dasAudio_04_spatial_audio.rst +++ b/doc/source/reference/tutorials/dasAudio_04_spatial_audio.rst @@ -81,16 +81,31 @@ Attenuation Models ================== Four built-in attenuation models control how volume decreases with distance. -Each constructor takes a ``max_distance`` parameter: - -==================================== ============================== ========================================== -Constructor Falloff Character -==================================== ============================== ========================================== -``inverse_distance_attenuation`` 1 / *d* Natural, gradual rolloff -``linear_attenuation`` 1 - *d* / *max* Straight line to silence at max distance -``quadratic_attenuation`` 1 - (*d* / *max*)\ :sup:`2` Faster than linear near max distance -``inverse_square_attenuation`` 1 / *d*\ :sup:`2` Physically accurate, rapid falloff -==================================== ============================== ========================================== +``linear_attenuation`` and ``quadratic_attenuation`` take a ``max_distance`` +parameter (distance at which volume reaches zero). +``inverse_distance_attenuation`` and ``inverse_square_attenuation`` take a +``dmin`` reference distance (volume is 1.0 at that distance, rolling off +beyond it): + +.. list-table:: + :header-rows: 1 + :widths: 32 38 30 + + * - Constructor + - Falloff + - Character + * - ``inverse_distance_attenuation`` + - *dmin* / (*d* + *dmin*) + - Natural rolloff; full volume at *dmin* + * - ``linear_attenuation`` + - 1 - *d* / *max* + - Straight line to silence at *max* + * - ``quadratic_attenuation`` + - 1 - (*d* / *max*)\ :sup:`2` + - Faster than linear near *max* + * - ``inverse_square_attenuation`` + - *dmin*\ :sup:`2` / (*d*\ :sup:`2` + *dmin*\ :sup:`2`) + - Smooth near *dmin*, rapid falloff Example --- comparing all four at different distances: diff --git a/doc/source/reference/tutorials/dasAudio_05_reverb.rst b/doc/source/reference/tutorials/dasAudio_05_reverb.rst index 2d70d67ac..41831a4ad 100644 --- a/doc/source/reference/tutorials/dasAudio_05_reverb.rst +++ b/doc/source/reference/tutorials/dasAudio_05_reverb.rst @@ -113,7 +113,7 @@ The ``ma_chorus_config`` fields are: Field Description ============== ============================================ ``rate`` LFO modulation rate in Hz -``depth`` LFO modulation depth (0.0--1.0) +``depth`` LFO modulation depth in milliseconds (default 8.0 ms) ``delay_ms`` Base delay in milliseconds ``feedback`` Feedback amount (0.0--1.0) ``wet`` Wet/dry mix (0.0 = dry, 1.0 = fully wet) diff --git a/doc/source/reference/tutorials/dasAudio_06_streaming.rst b/doc/source/reference/tutorials/dasAudio_06_streaming.rst index fd3bd5e75..5b0b89d92 100644 --- a/doc/source/reference/tutorials/dasAudio_06_streaming.rst +++ b/doc/source/reference/tutorials/dasAudio_06_streaming.rst @@ -75,30 +75,26 @@ remains continuous: When the sweep is finished, ``stop(sid, 0.1)`` fades out and releases the stream. -Collected Audio System -====================== +Rising Chord +============ -``with_audio_system`` is the simplest way to initialise audio, but for -long-running applications (games, tools) where sounds are created and -destroyed over time, ``with_collected_audio_system`` adds garbage collection -for audio resources. When using it, call ``audio_system_collect`` -periodically to process deferred cleanup: +The second section of the tutorial streams a rising chord: 10 chunks of +100 ms each, with the frequency stepping up by 44 Hz per chunk. Each +chunk is generated and fed independently via ``append_to_pcm``: .. code-block:: das - with_collected_audio_system() { - let sid = play_sound_from_pcm_stream(MA_SAMPLE_RATE, 1) - for (i in range(10)) { - var samples <- [for (x in range(chunk_samples)); - sin(2.0 * PI * 440.0 * float(x) / float(MA_SAMPLE_RATE)) * 0.4 - ] - append_to_pcm(sid, samples) - audio_system_collect() - sleep(100u) - } - stop(sid, 0.1) - audio_system_collect() + let sid = play_sound_from_pcm_stream(MA_SAMPLE_RATE, 1) + let chunk_samples = MA_SAMPLE_RATE / 10 // 100ms + for (i in range(10)) { + let freq = 440.0 + float(i) * 44.0 + var samples <- [for (x in range(chunk_samples)); + sin(2.0 * PI * freq * float(x) / float(MA_SAMPLE_RATE)) * 0.4 + ] + append_to_pcm(sid, samples) + sleep(100u) } + stop(sid, 0.1) Running the Tutorial ==================== @@ -108,8 +104,8 @@ Run from the project root:: daslang.exe tutorials/dasAudio/06_streaming.das The tutorial plays a 3-second frequency sweep (220 Hz to 880 Hz), printing -the current frequency every 10 chunks, then demonstrates the collected -audio system with a short ascending tone sequence. +the current frequency every 10 chunks, then plays a 1-second ascending +tone sequence built from 10 streamed chunks. .. seealso:: diff --git a/doc/source/reference/tutorials/dasHV_01_http_requests.rst b/doc/source/reference/tutorials/dasHV_01_http_requests.rst index fe8cb0625..fe9cb0afb 100644 --- a/doc/source/reference/tutorials/dasHV_01_http_requests.rst +++ b/doc/source/reference/tutorials/dasHV_01_http_requests.rst @@ -125,13 +125,17 @@ Status Message Binary Data =========== -``resp.body`` is a string, but ``resp.content_length`` tells you the -exact byte count for binary payloads: +``get_body_bytes(resp)`` extracts the response body as ``array`` — +use it when the server returns non-text binary content such as images +or raw data files. ``resp.content_length`` gives the byte count without +copying: .. code-block:: das - GET(url) <| $(resp) { - print("Received {resp.content_length} bytes\n") + POST(url, "binary test data") $(resp) { + var bytes <- get_body_bytes(resp) + print("Received {length(bytes)} bytes\n") + delete bytes } Quick Reference @@ -155,6 +159,7 @@ Function Description ``get_header(resp, key)`` Read a single response header ``each_header(resp) <| $(k, v) {}`` Iterate all headers (including Set-Cookie) ``status_message(resp)`` Human-readable status phrase +``get_body_bytes(resp)`` Response body as ``array`` ==================================== ============================================== .. seealso:: diff --git a/doc/source/reference/tutorials/dasHV_03_http_server.rst b/doc/source/reference/tutorials/dasHV_03_http_server.rst index 12bdd2567..827f5309a 100644 --- a/doc/source/reference/tutorials/dasHV_03_http_server.rst +++ b/doc/source/reference/tutorials/dasHV_03_http_server.rst @@ -164,9 +164,9 @@ string to ``JSON(resp, ...)``: .. note:: - ``JSON()`` always returns status 200. For non-200 JSON responses, - set the body and content-type manually — see - :ref:`tutorial_dasHV_http_server_advanced`. + ``JSON()`` defaults to status 200 but accepts an optional third argument: + ``JSON(resp, json_string, http_status.NOT_FOUND)``. See + :ref:`tutorial_dasHV_http_server_advanced` for examples. Server Lifecycle ================ @@ -190,6 +190,7 @@ Start the server on a background thread, run your test code, then stop: sleep(10u) } server->stop() + server->cleanup() finished |> notify_and_release } started |> join diff --git a/doc/source/reference/tutorials/dasHV_04_http_server_advanced.rst b/doc/source/reference/tutorials/dasHV_04_http_server_advanced.rst index 145fcddb5..da9880408 100644 --- a/doc/source/reference/tutorials/dasHV_04_http_server_advanced.rst +++ b/doc/source/reference/tutorials/dasHV_04_http_server_advanced.rst @@ -102,7 +102,7 @@ Content-Type and Status Codes ============================= ``set_content_type(resp, type)`` -give full control over the response: +gives full control over the response: .. code-block:: das diff --git a/doc/source/reference/tutorials/dasHV_05_cookies_and_forms.rst b/doc/source/reference/tutorials/dasHV_05_cookies_and_forms.rst index 764691f77..deb7340d3 100644 --- a/doc/source/reference/tutorials/dasHV_05_cookies_and_forms.rst +++ b/doc/source/reference/tutorials/dasHV_05_cookies_and_forms.rst @@ -200,8 +200,6 @@ Quick Reference * - ``get_url_encoded(req_ptr, key)`` - Get URL-encoded form field (server) -Source: ``tutorials/dasHV/05_cookies_and_forms.das`` - .. seealso:: Full source: :download:`tutorials/dasHV/05_cookies_and_forms.das <../../../../tutorials/dasHV/05_cookies_and_forms.das>` diff --git a/doc/source/reference/tutorials/dasOPENAI_01_first_chat.rst b/doc/source/reference/tutorials/dasOPENAI_01_first_chat.rst index c9aea38dc..736ffc3ff 100644 --- a/doc/source/reference/tutorials/dasOPENAI_01_first_chat.rst +++ b/doc/source/reference/tutorials/dasOPENAI_01_first_chat.rst @@ -45,8 +45,8 @@ Constructing a client // local server, no key let client = openai_client("http://localhost:11434/v1") - // real OpenAI, key from the environment - let client = openai_client("https://api.openai.com/v1", get_env("OPENAI_API_KEY")) + // real OpenAI, key from the environment (requires daslib/fio) + let client = openai_client("https://api.openai.com/v1", get_env_variable("OPENAI_API_KEY")) The chat_text one-liner ======================= diff --git a/doc/source/reference/tutorials/dasOPENAI_03_structured_outputs.rst b/doc/source/reference/tutorials/dasOPENAI_03_structured_outputs.rst index 97c767a67..132c082a9 100644 --- a/doc/source/reference/tutorials/dasOPENAI_03_structured_outputs.rst +++ b/doc/source/reference/tutorials/dasOPENAI_03_structured_outputs.rst @@ -24,7 +24,7 @@ Assign it to the request: var req = ChatCompletionRequest(model = "gpt-4o-mini", messages <- [ChatMessage(role = "user", - content = "Return the answer to 6*7 as JSON: {\"answer\": N}.")]) + content = "Return the answer to 6*7 as JSON: \{\"answer\": N\}.")]) req.response_format = json_object_format() let res = chat(client, req) diff --git a/doc/source/reference/tutorials/dasPEG_03_csv_parser.rst b/doc/source/reference/tutorials/dasPEG_03_csv_parser.rst index e4dd04a78..28889eb8e 100644 --- a/doc/source/reference/tutorials/dasPEG_03_csv_parser.rst +++ b/doc/source/reference/tutorials/dasPEG_03_csv_parser.rst @@ -15,7 +15,7 @@ This tutorial builds a CSV parser that demonstrates collection-oriented PEG features. You will learn: - ``*rule`` (zero-or-more) and ``+rule`` (one-or-more) repetition -- ``!rule`` (negative lookahead) +- ``not_set()`` for inverted character classes - ``any``, ``EOL``, ``TS`` terminals - ``void?`` pattern rules - ``string_`` and ``double_`` built-in terminals @@ -135,20 +135,27 @@ Terminal Description ``TS`` Zero or more tabs/spaces (no newlines) ============================ ============================================= -Negative Lookahead -================== +Inverted Character Sets (not_set) +================================== -``!rule`` succeeds when ``rule`` does **not** match, without consuming -input. Useful for "match anything except": +``not_set(chars...)`` matches any single character **not** in the given +set. It is the complement of ``set()`` and is useful for "match +anything except a specific character": .. code-block:: das - // Match any character that is not a newline + // Match any character that is not a newline or semicolon var expr_text : void? rule(not_set('\n', '\r', ';')) { return null } +``not_set()`` accepts the same range and single-character arguments as +``set()``. Unlike ``!rule`` (negative lookahead, covered in +:ref:`tutorial_dasPEG_basic_interpreter`), ``not_set()`` is a +*terminal* --- it always advances the parser by exactly one character +when it matches. + .. seealso:: Full source: :download:`tutorials/dasPEG/03_csv_parser.das <../../../../tutorials/dasPEG/03_csv_parser.das>` diff --git a/doc/source/reference/tutorials/dasPUGIXML_05_linq.rst b/doc/source/reference/tutorials/dasPUGIXML_05_linq.rst index 0b940a826..1af204f99 100644 --- a/doc/source/reference/tutorials/dasPUGIXML_05_linq.rst +++ b/doc/source/reference/tutorials/dasPUGIXML_05_linq.rst @@ -38,6 +38,7 @@ attribute is absent keeps the struct's declared default: "" + "" + "" + + "" + "") parse_xml(CATALOG) $(doc, ok) { @@ -49,6 +50,7 @@ attribute is absent keeps the struct's declared default: // #1 Audi (2021) // #2 Toyota (2020) // #3 Ford (2000) <- year attribute absent, default kept + // #4 Kia (2022) Supported field types are the XML scalar attribute types: ``int``, ``uint``, ``float``, ``double``, ``bool``, ``string``. Fields of other types keep their @@ -67,7 +69,7 @@ The iterator is just an iterable, so comprehensions filter and project directly: car.make; where car.in_stock && car.price < 40000.0] print("{affordable}\n") - // [ Toyota] + // [ Toyota, Kia] } Reverse iteration diff --git a/doc/source/reference/tutorials/dasStbImage_01_loading_images.rst b/doc/source/reference/tutorials/dasStbImage_01_loading_images.rst index 52fe8203b..68ce4c178 100644 --- a/doc/source/reference/tutorials/dasStbImage_01_loading_images.rst +++ b/doc/source/reference/tutorials/dasStbImage_01_loading_images.rst @@ -44,7 +44,7 @@ Once loaded, an ``Image`` exposes several query methods: ============================ ============================================= Method Description ============================ ============================================= -``valid()`` ``true`` if width > 0 and height > 0 +``valid()`` ``true`` if width > 0, height > 0, and pixel data is non-empty ``stride()`` Bytes per row (width * channels * bpc) ``has_alpha()`` ``true`` if channels is 2 or 4 ``pixel_size()`` Bytes per pixel (channels * bpc) diff --git a/doc/source/reference/tutorials/dasStbImage_03_transforms.rst b/doc/source/reference/tutorials/dasStbImage_03_transforms.rst index 44355b77a..3b7e601f6 100644 --- a/doc/source/reference/tutorials/dasStbImage_03_transforms.rst +++ b/doc/source/reference/tutorials/dasStbImage_03_transforms.rst @@ -57,8 +57,8 @@ Blitting ======== ``Image.blit(source, x, y)`` copies source pixels onto the image at -position (x, y). Both images must have the same number of channels. -Pixels outside the destination bounds are clipped: +position (x, y). Both images must have the same number of channels and +the same ``bpc``. Pixels outside the destination bounds are clipped: .. code-block:: das diff --git a/doc/source/reference/tutorials/dasStbImage_05_drawing_and_blending.rst b/doc/source/reference/tutorials/dasStbImage_05_drawing_and_blending.rst index 4b3858c8f..332141e26 100644 --- a/doc/source/reference/tutorials/dasStbImage_05_drawing_and_blending.rst +++ b/doc/source/reference/tutorials/dasStbImage_05_drawing_and_blending.rst @@ -84,6 +84,11 @@ is a common pattern in UI rendering: .. code-block:: das + // Helper: pack RGBA channels into a single uint (R in low byte, A in high byte) + def pack_rgba(r, g, b, a : int) : uint { + return uint(r) | (uint(g) << 8u) | (uint(b) << 16u) | (uint(a) << 24u) + } + var canvas = make_image(64, 48, 4) // Background canvas.fill_rect(0, 0, 64, 48, pack_rgba(30, 30, 50, 255)) diff --git a/doc/source/reference/tutorials/integration_c_03_binding_types.rst b/doc/source/reference/tutorials/integration_c_03_binding_types.rst index 6accff301..efcf0807b 100644 --- a/doc/source/reference/tutorials/integration_c_03_binding_types.rst +++ b/doc/source/reference/tutorials/integration_c_03_binding_types.rst @@ -42,8 +42,10 @@ Binding an enumeration das_enumeration_add_value(en, "blue", "Color_blue", Color_blue); das_module_bind_enumeration(mod, en); -The third argument to ``das_enumeration_make`` selects the underlying -integer type: ``0`` = int8, ``1`` = int32, ``2`` = int16. +The third argument to ``das_enumeration_make`` is ``ext`` (boolean): +non-zero means the enum's underlying storage is ``int64``; zero (the +common case) means ``int``. The example above passes ``1``, so ``Color`` +is int64-backed — pass ``0`` for the usual ``int`` storage. In daslang: @@ -145,7 +147,7 @@ Building and running Expected output:: color = green - numbers = [10, 20, 30] + numbers = [ 10, 20, 30] point = (3, 4) distance from origin = 5 as string: (3.00, 4.00) diff --git a/doc/source/reference/tutorials/integration_c_05_unaligned_advanced.rst b/doc/source/reference/tutorials/integration_c_05_unaligned_advanced.rst index 809134572..907a44973 100644 --- a/doc/source/reference/tutorials/integration_c_05_unaligned_advanced.rst +++ b/doc/source/reference/tutorials/integration_c_05_unaligned_advanced.rst @@ -144,7 +144,11 @@ Expected output (Part 2 — error reporting):: === Part 2: Error reporting === Compilation produced 1 error(s): - error 0: bad_script.das:4:19: ... + error 0: error[30344]: local variable x initialization type mismatch; int const = string const + bad_script.das:4:8 + let x : int = "not an int" + ^ + not the same type .. seealso:: diff --git a/doc/source/reference/tutorials/integration_c_06_sandbox.rst b/doc/source/reference/tutorials/integration_c_06_sandbox.rst index 2ce9cc9dd..f0a3218e3 100644 --- a/doc/source/reference/tutorials/integration_c_06_sandbox.rst +++ b/doc/source/reference/tutorials/integration_c_06_sandbox.rst @@ -309,35 +309,35 @@ Expected output (Part 1 — valid script):: clamped: 100 Hello, sandbox! - split: [[ a; b; c]] + split: [ a, b, c] Expected output (Part 2 — banned option):: === Part 2: Banned option === Compilation produced 1 error(s): - error 0: option persistent_heap is not allowed here ... + error 0: error[50100]: option persistent_heap is not allowed here ... Expected output (Part 3 — unsafe forbidden):: === Part 3: Unsafe block forbidden === Compilation produced 1 error(s): - error 0: unsafe function test ... + error 0: error[31012]: unsafe function test ... Expected output (Part 4 — banned module):: === Part 4: Banned module === Compilation produced 1 error(s): - error 0: module not allowed 'fio' ... + error 0: error[20605]: module not allowed 'fio_core' ... Expected output (Part 5 — CodeOfPolicies via C API):: === Part 5: CodeOfPolicies via C API === Compilation with DAS_POLICY_NO_UNSAFE produced 1 error(s): - error 0: unsafe function test ... + error 0: error[31012]: unsafe function test ... Part 5 uses ``das_policies_make`` and ``das_policies_set_bool`` to set ``DAS_POLICY_NO_UNSAFE`` directly from C, without a ``.das_project`` diff --git a/doc/source/reference/tutorials/integration_c_08_serialization.rst b/doc/source/reference/tutorials/integration_c_08_serialization.rst index b6dc1167a..c52972a80 100644 --- a/doc/source/reference/tutorials/integration_c_08_serialization.rst +++ b/doc/source/reference/tutorials/integration_c_08_serialization.rst @@ -92,7 +92,7 @@ Expected output:: Simulated successfully. === Stage 2: Serialize === - Serialized size: 5022 bytes + Serialized size: NNN bytes Original program released. === Stage 3: Deserialize === diff --git a/doc/source/reference/tutorials/integration_c_13_shared_module.rst b/doc/source/reference/tutorials/integration_c_13_shared_module.rst index 535520c57..32d29a686 100644 --- a/doc/source/reference/tutorials/integration_c_13_shared_module.rst +++ b/doc/source/reference/tutorials/integration_c_13_shared_module.rst @@ -36,7 +36,7 @@ the file and fails with ``missing prerequisite; file not found``: das_file_access * fa2 = das_fileaccess_make_default(); das_fileaccess_introduce_file(fa2, "user_script.das", SCRIPT_SRC, 0); das_program * p2 = das_program_compile("user_script.das", fa2, tout, libgrp); - // --> error[30901]: missing prerequisite 'my_helpers'; file not found + // --> error[20605]: missing prerequisite 'my_helpers'; file not found The solution — ``module X shared`` @@ -161,7 +161,7 @@ Expected output:: Part 1a: same FileAccess -- compilation succeeded (0 errors) Part 1b: different FileAccess, module file missing -- compilation failed (1 error): - error 0: error[30901]: missing prerequisite 'my_helpers'; file not found ... + error 0: error[20605]: missing prerequisite 'my_helpers'; file not found ... === Part 2: The Solution -- module my_helpers shared === diff --git a/doc/source/reference/tutorials/integration_cpp_03_binding_functions.rst b/doc/source/reference/tutorials/integration_cpp_03_binding_functions.rst index 7c3803828..4ae71ab5b 100644 --- a/doc/source/reference/tutorials/integration_cpp_03_binding_functions.rst +++ b/doc/source/reference/tutorials/integration_cpp_03_binding_functions.rst @@ -172,7 +172,7 @@ In the script the function takes zero arguments: .. code-block:: das - print_stack_info() // prints "Stack size: 16384 bytes" + print_stack_info() // prints "Context stack size: 16384 bytes" Activating the module in the host diff --git a/doc/source/reference/tutorials/integration_cpp_06_interop.rst b/doc/source/reference/tutorials/integration_cpp_06_interop.rst index 70c010d15..e4bfc3fea 100644 --- a/doc/source/reference/tutorials/integration_cpp_06_interop.rst +++ b/doc/source/reference/tutorials/integration_cpp_06_interop.rst @@ -14,7 +14,7 @@ This tutorial covers ``addInterop`` — the low-level alternative to * Accepting "any type" arguments (``vec4f`` template parameter) * Runtime ``TypeInfo`` inspection via ``call->types[i]`` * Accessing call-site debug info via ``call->debugInfo`` -* The ``TypeInfo`` union: ``structType`` / ``enumType`` / ``annotation_or_name`` +* The ``TypeInfo`` union: ``structType`` / ``enumType`` / ``annotation_info`` Prerequisites diff --git a/doc/source/reference/tutorials/integration_cpp_07_callbacks.rst b/doc/source/reference/tutorials/integration_cpp_07_callbacks.rst index 09fd3798d..e1af06d0d 100644 --- a/doc/source/reference/tutorials/integration_cpp_07_callbacks.rst +++ b/doc/source/reference/tutorials/integration_cpp_07_callbacks.rst @@ -162,7 +162,7 @@ Function pointers use ``@@function_name``: call_function_twice(@@print_value, 5) // Fibonacci iteration - for_each_fibonacci(5) $(index, value) { + for_each_fibonacci(8) $(index, value) { print("fib({index}) = {value}\n") } diff --git a/doc/source/reference/tutorials/integration_cpp_13_aot.rst b/doc/source/reference/tutorials/integration_cpp_13_aot.rst index a94c3f9f8..9c70fd08a 100644 --- a/doc/source/reference/tutorials/integration_cpp_13_aot.rst +++ b/doc/source/reference/tutorials/integration_cpp_13_aot.rst @@ -92,7 +92,7 @@ to automate both generation and compilation: add_custom_target(integration_cpp_13_dasAotStub) # 2. Generate AOT C++ from the .das file using daslang as the tool - DAS_AOT("tutorials/integration/cpp/13_aot.das" + DAS_AOT("integration/cpp/13_aot.das" INTEGRATION_13_AOT_GENERATED_SRC integration_cpp_13_dasAotStub daslang) @@ -101,8 +101,8 @@ to automate both generation and compilation: 13_aot.cpp ${INTEGRATION_13_AOT_GENERATED_SRC} ) - TARGET_LINK_LIBRARIES(integration_cpp_13 libDaScript Threads::Threads) - ADD_DEPENDENCIES(integration_cpp_13 libDaScript + TARGET_LINK_LIBRARIES(integration_cpp_13 libDaScriptDyn Threads::Threads) + ADD_DEPENDENCIES(integration_cpp_13 libDaScriptDyn integration_cpp_13_dasAotStub) The ``DAS_AOT`` macro: diff --git a/doc/source/reference/tutorials/integration_cpp_15_custom_annotations.rst b/doc/source/reference/tutorials/integration_cpp_15_custom_annotations.rst index 24ebca813..60eaad974 100644 --- a/doc/source/reference/tutorials/integration_cpp_15_custom_annotations.rst +++ b/doc/source/reference/tutorials/integration_cpp_15_custom_annotations.rst @@ -31,7 +31,8 @@ daslang with the ``[annotation_name]`` syntax. They let the host application validate, modify, or transform script code during compilation. -Built-in examples: ``[export]``, ``[private]``, ``[deprecated]``. +Built-in examples: ``[export]``, ``[deprecated]`` (note: ``private`` is a +visibility keyword, not an annotation). This tutorial shows how to create your own. @@ -197,6 +198,7 @@ Using from daslang // Monster now has an "id" field added by [add_field] var m = Monster(name = "Dragon", hp = 500, id = 42) print("{m.name}: id={m.id}\n") + } Building and running diff --git a/doc/source/reference/tutorials/integration_cpp_16_sandbox.rst b/doc/source/reference/tutorials/integration_cpp_16_sandbox.rst index 6c19ac2ea..bc395c006 100644 --- a/doc/source/reference/tutorials/integration_cpp_16_sandbox.rst +++ b/doc/source/reference/tutorials/integration_cpp_16_sandbox.rst @@ -156,16 +156,21 @@ Loading a project file from C++ string projectPath = getDasRoot() + "/path/to/sandbox.das_project"; - auto fAccess = make_smart( - projectPath, make_smart()); + + // get_file_access is a link-time resolved function declared in the host. + // It compiles the project file, simulates it, and returns a FileAccess + // that calls the exported callback functions during compilation. + das::FileAccessPtr get_file_access(char * pak); // declared at file scope + + auto fAccess = get_file_access((char *) projectPath.c_str()); CodeOfPolicies policies; auto program = compileDaScript(scriptPath, fAccess, tout, libGroup, policies); -The ``FsFileAccess(projectPath, fallbackAccess)`` constructor -compiles and simulates the project file, then looks up exported -callback functions by name. +``get_file_access`` (declared at file scope and linked in from the +daslang library) compiles and simulates the project file, then returns +a ``FileAccessPtr`` whose callbacks delegate to the exported functions. Project file callbacks ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/reference/tutorials/integration_cpp_19_class_adapters.rst b/doc/source/reference/tutorials/integration_cpp_19_class_adapters.rst index fa01f5bad..3f8e0ebde 100644 --- a/doc/source/reference/tutorials/integration_cpp_19_class_adapters.rst +++ b/doc/source/reference/tutorials/integration_cpp_19_class_adapters.rst @@ -144,7 +144,7 @@ byte arrays). The module loads it with ``compileBuiltinModule``: addExtern(*this, lib, "add_object", SideEffects::modifyExternal, "addObject"); - compileBuiltinModule("class_adapters_module.das", + compileBuiltinModule(this, "class_adapters_module.das", class_adapters_module_das, sizeof(class_adapters_module_das)); } @@ -167,29 +167,39 @@ to provide implementations: class ExampleObject : TutorialBaseClass { position : float3 - speed : float + velocity : float3 + gravity : float = 9.8f + name : string def override update(dt : float) : void { - position.x += speed * dt + print(" [das] {name}.update({dt})\n") + position += velocity * dt + velocity.y -= gravity * dt } - def override get_position() : float3 { + def override get_position : float3 { + print(" [das] {name}.get_position => {position}\n") return position } } + // Helper: registers the object with C++ via the Context* injected argument. + // The Context* parameter is injected automatically — do not pass it explicitly. + def add_new_object(classPtr) { + add_object(classPtr, class_info(*classPtr)) + } + [export] def test { - var obj = new ExampleObject() - obj.position = float3(0.0, 0.0, 0.0) - obj.speed = 10.0 - - unsafe { - add_object(addr(*obj), class_info(*obj), this_context()) + print("tick(0.0) = {tick(0.0)}\n\n") // no objects yet + add_new_object(new ExampleObject(name = "A")) + for (t in range(3)) { + print("tick(0.1) = {tick(0.1)}\n\n") + } + add_new_object(new ExampleObject(name = "B", velocity = float3(1.0, 0.0, 0.0))) + for (t in range(3)) { + print("tick(0.1) = {tick(0.1)}\n\n") } - - let avg = tick(0.5) - print("After tick(0.5): avg position = ({avg.x}, {avg.y}, {avg.z})\n") } @@ -201,9 +211,21 @@ Build & run cmake --build build --config Release --target integration_cpp_19 bin/Release/integration_cpp_19 -Expected output:: - - After tick(0.5): avg position = (5, 0, 0) +C++ drives each object's overridden ``update`` / ``get_position`` through the +adapter every tick, so the run is interleaved with per-object traces +(abridged):: + + tick(0.0) = 0,0,0 + + [das] A.update(0.1) + [das] A.get_position => 0,0,0 + tick(0.1) = 0,0,0 + ... + [das] A.update(0.1) + [das] A.get_position => 0,-0.58800006,0 + [das] B.update(0.1) + [das] B.get_position => 0.1,0,0 + tick(0.1) = 0.05,-0.29400003,0 .. seealso:: diff --git a/doc/source/reference/tutorials/integration_cpp_20_standalone_contexts.rst b/doc/source/reference/tutorials/integration_cpp_20_standalone_contexts.rst index 897905432..c6922674d 100644 --- a/doc/source/reference/tutorials/integration_cpp_20_standalone_contexts.rst +++ b/doc/source/reference/tutorials/integration_cpp_20_standalone_contexts.rst @@ -72,16 +72,19 @@ The ``DAS_AOT_CTX`` CMake macro drives the generation: .. code-block:: cmake - SET(STANDALONE_CTX_GENERATED_SRC) - DAS_AOT_CTX("tutorials/integration/cpp/standalone_context.das" - STANDALONE_CTX_GENERATED_SRC + SET(INTEGRATION_20_STANDALONE_CTX_GENERATED_SRC) + add_custom_target(integration_cpp_20_dasAotStubStandalone) + DAS_AOT_CTX("integration/cpp/standalone_context.das" + INTEGRATION_20_STANDALONE_CTX_GENERATED_SRC integration_cpp_20_dasAotStubStandalone daslang) add_executable(integration_cpp_20 20_standalone_context.cpp standalone_context.das - ${STANDALONE_CTX_GENERATED_SRC}) - TARGET_LINK_LIBRARIES(integration_cpp_20 libDaScript) + ${INTEGRATION_20_STANDALONE_CTX_GENERATED_SRC}) + TARGET_LINK_LIBRARIES(integration_cpp_20 libDaScriptDyn Threads::Threads) + ADD_DEPENDENCIES(integration_cpp_20 libDaScriptDyn + integration_cpp_20_dasAotStubStandalone) This runs ``daslang`` with the ``-ctx`` flag, which invokes ``daslib/aot_standalone.das`` to generate: diff --git a/doc/source/reference/tutorials/jsonrpc_01_request_response.rst b/doc/source/reference/tutorials/jsonrpc_01_request_response.rst index 94cb132f9..d6f092bee 100644 --- a/doc/source/reference/tutorials/jsonrpc_01_request_response.rst +++ b/doc/source/reference/tutorials/jsonrpc_01_request_response.rst @@ -77,7 +77,7 @@ Parsing a success response let r = parse_response(wire) // r.is_success = true // r.id_str = "1" - // r.result_json = ["hello"] + // r.result_json = ["hello"] (may be pretty-printed — use compact_json_whitespace if needed) Parsing an error response ========================== diff --git a/doc/source/reference/tutorials/jsonrpc_03_batch.rst b/doc/source/reference/tutorials/jsonrpc_03_batch.rst index 1c6feb3b7..affca5ac9 100644 --- a/doc/source/reference/tutorials/jsonrpc_03_batch.rst +++ b/doc/source/reference/tutorials/jsonrpc_03_batch.rst @@ -35,7 +35,7 @@ the notification produces no response and input order is preserved. if (m == "echo") return jsonrpc::compact_json_whitespace(p) return "\"unknown\"" } - // resp = [{"id":1,"result":"pong"},{"id":2,"result":[42]}] + // resp = [{"jsonrpc":"2.0","id":1,"result":"pong"},{"jsonrpc":"2.0","id":2,"result":[42]}] Per-entry errors: continue-on-error ==================================== diff --git a/doc/source/reference/tutorials/macros/08_variant_macro.rst b/doc/source/reference/tutorials/macros/08_variant_macro.rst index 25a61fada..9c797d2da 100644 --- a/doc/source/reference/tutorials/macros/08_variant_macro.rst +++ b/doc/source/reference/tutorials/macros/08_variant_macro.rst @@ -127,11 +127,11 @@ implements the interface → return ``true``. Otherwise → ``false``: let getter_field = "get`{iname}" var st = vtype.firstType.structType for (fld in st.fields) { - if (string(fld.name) == getter_field) { - return <- qmacro(true) + if (fld.name == getter_field) { + return <- quote(true) } } - return <- qmacro(false) + return <- quote(false) The result is a **compile-time constant** — no runtime cost at all. ``w is IDrawable`` becomes the literal ``true`` in the final program. @@ -163,13 +163,12 @@ getter: .. code-block:: das - var val = clone_expression(expr.value) - return <- qmacro($e(val) != null ? $c(func_name)(*$e(expr.value)) : null) + return <- qmacro($e(expr.value) != null ? $c(func_name)(*$e(expr.value)) : null) -``clone_expression`` is needed because the value expression appears -twice — once in the null check and once in the getter call. The -original ``expr.value`` is *moved* into the ``$e()`` splice, so a -clone provides the second copy. +``$e(expr.value)`` appears twice without a manual clone because +``apply_template`` (called internally by ``qmacro``) clones every +``$e(...)`` substitution independently — each splice site gets its +own deep copy of the expression. ``w ?as IDrawable`` becomes:: @@ -275,7 +274,7 @@ The full sequence for ``w is IDrawable``: InterfaceAsIs checks: Widget? → pointer to struct ✓ "IDrawable" is an [interface] ✓, Widget has get`IDrawable field ✓ ↓ - return qmacro(true) — replaces ExprIsVariant with ExprConstBool + return quote(true) — replaces ExprIsVariant with ExprConstBool ↓ simulate → constant folded, zero runtime cost @@ -327,12 +326,12 @@ Key takeaways - Check ``expr.value._type`` before claiming an expression * - ``find_unique_structure`` - Look up a struct by name from the compiling program - * - ``qmacro(true)`` + * - ``quote(true)`` / ``quote(false)`` - Generate a compile-time constant — zero runtime cost * - ``$c(name)(args)`` - Generate a function call by name in ``qmacro`` - * - ``clone_expression`` - - Deep-copy an expression for safe double use in ``qmacro`` + * - ``apply_template`` clone discipline + - ``qmacro`` clones each ``$e(...)`` splice independently; no manual pre-clone needed .. seealso:: diff --git a/doc/source/reference/tutorials/macros/12_typeinfo_macro.rst b/doc/source/reference/tutorials/macros/12_typeinfo_macro.rst index 0421006df..2af868996 100644 --- a/doc/source/reference/tutorials/macros/12_typeinfo_macro.rst +++ b/doc/source/reference/tutorials/macros/12_typeinfo_macro.rst @@ -163,8 +163,9 @@ Key points: - ``expr.typeexpr.enumType.list`` iterates all ``EnumEntry`` nodes. - The bare block provides a lexical scope for the intermediate variable inside the loop. -- The result is a **fixed-size array** (``string[N]``), not a dynamic - ``array``. +- The result is a **dynamic array** (``array``), not a + fixed-size ``string[N]`` — ``ExprMakeArray`` always produces a + dynamic array. has_non_static_method — returning a bool with subtrait diff --git a/doc/source/reference/tutorials/macros/13_enumeration_macro.rst b/doc/source/reference/tutorials/macros/13_enumeration_macro.rst index ccb9a4a12..470cdf138 100644 --- a/doc/source/reference/tutorials/macros/13_enumeration_macro.rst +++ b/doc/source/reference/tutorials/macros/13_enumeration_macro.rst @@ -182,7 +182,7 @@ three things at compile time: Both constructors are generated with ``qmacro_function``, registered with ``add_function(compiling_module(), fn)``, and marked as -non-private with ``enumFn.flags &= ~FunctionFlags.privateFunction``. +non-private with ``enumFn.flags.privateFunction = false``. How string_to_enum works internally @@ -215,7 +215,7 @@ demonstrates the **code generation** pattern for enumeration macros: } return $i(varName)?[src] ?? default<$t(enumT)> } - enumFn.flags &= ~FunctionFlags.privateFunction + enumFn.flags.privateFunction = false force_at(enumFn, enu.at) force_generated(enumFn, true) compiling_module() |> add_function(enumFn) @@ -224,7 +224,7 @@ demonstrates the **code generation** pattern for enumeration macros: $(src : string; defaultValue : $t(enumT)) : $t(enumT) { return $i(varName)?[src] ?? defaultValue } - enumFnDefault.flags &= ~FunctionFlags.privateFunction + enumFnDefault.flags.privateFunction = false force_at(enumFnDefault, enu.at) force_generated(enumFnDefault, true) compiling_module() |> add_function(enumFnDefault) diff --git a/doc/source/reference/tutorials/macros/15_type_macro.rst b/doc/source/reference/tutorials/macros/15_type_macro.rst index a0dd7927d..2cee3b383 100644 --- a/doc/source/reference/tutorials/macros/15_type_macro.rst +++ b/doc/source/reference/tutorials/macros/15_type_macro.rst @@ -21,7 +21,7 @@ inference. ``prog`` is the program being compiled. ``mod`` is the module that registered the macro. ``td`` is the ``TypeDecl`` node representing the macro invocation — - its ``dimExpr`` array carries the arguments. + its ``typeMacroExpr`` array carries the arguments. ``passT`` is non-null only in a generic context (see below). Return the resolved ``TypeDeclPtr``, or null on error. @@ -41,7 +41,7 @@ How the parser stores arguments When the compiler sees a type-macro invocation like ``padded(type, 4)`` it creates a ``TypeDecl`` with -``baseType = Type.typeMacro`` and populates the ``dimExpr`` array: +``baseType = Type.typeMacro`` and populates the ``typeMacroExpr`` array: .. list-table:: :header-rows: 1 @@ -50,18 +50,18 @@ When the compiler sees a type-macro invocation like * - Index - AST node - Contains - * - ``dimExpr[0]`` + * - ``typeMacroExpr[0]`` - ``ExprConstString`` - The macro name (``"padded"``) - * - ``dimExpr[1]`` + * - ``typeMacroExpr[1]`` - ``ExprTypeDecl`` - The first argument — the type (wraps ``type``) - * - ``dimExpr[2]`` + * - ``typeMacroExpr[2]`` - ``ExprConstInt`` - The second argument — the size (``4``) Type arguments are wrapped in ``ExprTypeDecl``; their inferred result is -available via ``dimExpr[i]._type``. Value arguments keep their +available via ``typeMacroExpr[i]._type``. Value arguments keep their expression type — ``ExprConstInt`` for integer literals, ``ExprConstString`` for strings, and so on. @@ -73,14 +73,14 @@ The compiler calls ``visit()`` in two different contexts: **Concrete** — all type arguments are already inferred. Example: ``var data : padded(type, 4)``. - Here ``td.dimExpr[1]._type`` is the fully-resolved ``float`` type, + Here ``td.typeMacroExpr[1]._type`` is the fully-resolved ``float`` type, and ``passT`` is null. The macro clones ``_type``, adds a dimension, and returns ``float[4]``. **Generic** — the type includes unresolved type parameters. Example: ``def foo(arr : padded(type, 4))``. - Here ``td.dimExpr[1]._type`` is null because the type has not + Here ``td.typeMacroExpr[1]._type`` is null because the type has not been inferred yet. ``passT`` is non-null and carries the actual argument type being matched. The macro must return a type the compiler can use for generic @@ -89,7 +89,7 @@ The compiler calls ``visit()`` in two different contexts: .. important:: - Always check ``td.dimExpr[i]._type == null`` to distinguish the + Always check ``td.typeMacroExpr[i]._type == null`` to distinguish the generic path from the concrete path. In the generic path, fall back to ``.typeexpr`` or create an ``autoinfer`` type. @@ -105,7 +105,7 @@ The type macro lives in its own module so that it is available via :caption: tutorials/macros/type_macro_mod.das The ``visit`` method first validates that exactly two user arguments -were provided (``dimExpr`` length 3 — the name plus two arguments) and +were provided (``typeMacroExpr`` length 3 — the name plus two arguments) and that the size argument is a constant integer. For the **generic path** (``_type == null``): if the first argument is an diff --git a/doc/source/reference/tutorials/macros/16_template_type_macro.rst b/doc/source/reference/tutorials/macros/16_template_type_macro.rst index 0a279557f..861e9a7a5 100644 --- a/doc/source/reference/tutorials/macros/16_template_type_macro.rst +++ b/doc/source/reference/tutorials/macros/16_template_type_macro.rst @@ -33,7 +33,7 @@ path the compiler reads them back to deduce ``auto(T1)``, **``[typemacro_function]``** annotation (from ``daslib/typemacro_boost``) — converts a regular function into an ``AstTypeMacro``. It auto-generates the class, registers it with ``add_new_type_macro``, and -extracts ``dimExpr`` arguments into the function parameters. The first +extracts ``typeMacroExpr`` arguments into the function parameters. The first two parameters are always ``macroArgument`` (the ``TypeDecl`` node) and ``passArgument`` (non-null in generic context); subsequent parameters are the user arguments. @@ -96,7 +96,7 @@ Section 1 — The macro module The ``[typemacro_function]`` annotation on ``pair`` converts the function into an ``AstTypeMacro`` registered under the name ``"pair"``. The annotation also generates code that extracts -``dimExpr[1]`` and ``dimExpr[2]`` into the ``T1`` and ``T2`` +``typeMacroExpr[1]`` and ``typeMacroExpr[2]`` into the ``T1`` and ``T2`` ``TypeDecl?`` parameters. ``pair`` builds its result from ``Pair`` and those two arguments, so it never reads the incoming ``macroArgument`` type — hence the ``unused_argument(macroArgument)`` diff --git a/doc/source/reference/tutorials/macros/18_with_boost.rst b/doc/source/reference/tutorials/macros/18_with_boost.rst index 7ff6c9ee2..70b6dd311 100644 --- a/doc/source/reference/tutorials/macros/18_with_boost.rst +++ b/doc/source/reference/tutorials/macros/18_with_boost.rst @@ -190,6 +190,8 @@ Expected output:: Previous tutorial: :ref:`tutorial_macro_qmacro` + Next tutorial: :ref:`tutorial_macro_add_module_option` + Standard library: ``daslib/with_boost.das`` Language reference: :ref:`Macros ` — full macro system documentation diff --git a/doc/source/reference/tutorials/macros/19_add_module_option.rst b/doc/source/reference/tutorials/macros/19_add_module_option.rst new file mode 100644 index 000000000..f6c54bf54 --- /dev/null +++ b/doc/source/reference/tutorials/macros/19_add_module_option.rst @@ -0,0 +1,128 @@ +.. _tutorial_macro_add_module_option: + +.. index:: + single: Tutorial; Macros; add_module_option + single: Tutorial; Macros; module options + single: Tutorial; Macros; this_module + +==================================================== + Macro Tutorial 19: Custom module options +==================================================== + +Most ``options`` are built into the compiler — ``gen2``, +``persistent_heap``, ``rtti``, and so on. A module can also define its +**own** option so that any file requiring it may set it, and a macro in the +module can read it to change behavior. This is the mechanism behind built-in +module options such as ``shader_like`` in ``daslib/validate_code``. + +The flow has two halves: + +- The module **registers** an option name and type with + ``add_module_option``. After that, the parser accepts + ``options = ...`` in any file that requires the module. +- A pass macro in the module **reads** the flag with ``find_arg`` and acts + on it. + + +Registering the option +======================= + +Full source: :download:`add_module_option_mod.das <../../../../../tutorials/macros/add_module_option_mod.das>` + +``add_module_option(module, name, type)`` records an option name and its +type on a module. It must run while the module's macros are being compiled, +so it lives in a ``macro_function`` guarded by +``is_compiling_macros_in_module``: + +.. code-block:: das + + [_macro, macro_function] + def register_options { + if (is_compiling_macros_in_module("add_module_option_mod")) { + this_module() |> add_module_option("trace_compile", Type.tBool) + } + } + +``Type.tBool`` declares the option as boolean; ``Type.tInt`` / +``Type.tString`` register integer and string options. ``this_module()`` +returns the module being compiled — the one that owns the option. + + +Reading the option +================== + +A ``[lint_macro]`` runs once per module compiled after this one. It reads +the flag off the program options and, when set, prints a per-module note at +compile time: + +.. code-block:: das + + [lint_macro] + class TraceCompileLint : AstPassMacro { + def override apply(prog : ProgramPtr; mod : Module?) : bool { + let on = prog._options |> find_arg("trace_compile") ?as tBool ?? false + if (!on) return false + let cm = compiling_module() + var nfun = 0 + cm |> for_each_function("") $(var func : FunctionPtr) { + nfun++ + } + let name = empty(cm.name) ? "
" : string(cm.name) + print("[trace_compile] module '{name}' — {nfun} function(s)\n") + return false // lint passes never modify the AST + } + } + +Key points: + +- ``prog._options |> find_arg("trace_compile")`` looks the option up in the + options of the module being linted. ``?as tBool`` extracts the boolean and + ``?? false`` supplies the default when the option was never set. +- ``[lint_macro]`` options are **per-module**: the flag is only ``true`` for + the module that actually set it, so the note prints once for that module. +- ``print(...)`` from a macro outputs at **compile time** — the note appears + before any runtime output. + + +The usage file +============== + +Full source: :download:`19_add_module_option.das <../../../../../tutorials/macros/19_add_module_option.das>` + +.. literalinclude:: ../../../../../tutorials/macros/19_add_module_option.das + :language: das + :lines: 23-36 + +The ``options trace_compile = true`` line is accepted only because +``add_module_option_mod`` registered the name — without the ``require``, the +parser would reject it as an unknown option. Flip the flag to ``false`` (or +delete the line) and the note vanishes; the program still compiles, because +the option name stays registered. + + +Output +====== + +.. code-block:: text + + [trace_compile] module '
' — 3 function(s) + Hello, world! + 2 + 3 = 5 + +The first line is emitted at compile time by the lint pass; the root script +module is unnamed, so it reports as ``
``, with its three functions +(``greet``, ``add``, ``main``). + + +.. seealso:: + + Full source: + :download:`19_add_module_option.das <../../../../../tutorials/macros/19_add_module_option.das>`, + :download:`add_module_option_mod.das <../../../../../tutorials/macros/add_module_option_mod.das>` + + Previous tutorial: :ref:`tutorial_macro_with_boost` + + Standard library: ``daslib/validate_code.das`` registers ``shader_like`` + the same way. + + Language reference: :ref:`Macros ` — full macro system documentation diff --git a/doc/source/reference/tutorials/sql_12_distinct.rst b/doc/source/reference/tutorials/sql_12_distinct.rst index 9db63ec55..eae30a924 100644 --- a/doc/source/reference/tutorials/sql_12_distinct.rst +++ b/doc/source/reference/tutorials/sql_12_distinct.rst @@ -60,24 +60,24 @@ smallest ``@sql_primary_key`` per group: .. code-block:: das - // Count distinct brands whose first-encountered Car has Year > 2009 + // Count distinct names whose first-encountered Car has Price > 150 let n = _sql(db |> select_from(type) - |> _distinct_by(_.Brand) - |> _count(_.Year > 2009)) - // SELECT COUNT(*) FROM (SELECT *, MIN("Id") FROM "Cars" GROUP BY "Brand") AS "t0" WHERE "Year" > ? + |> _distinct_by(_.Name) + |> _count(_.Price > 150)) + // SELECT COUNT(*) FROM (SELECT *, MIN("Id") FROM "Cars" GROUP BY "Name") AS "t0" WHERE "Price" > ? - // Same shape with long_count for int64 result + // Same shape with _long_count for int64 result let n64 = _sql(db |> select_from(type) - |> _distinct_by(_.Brand) - |> _long_count(_.Year > 2009)) + |> _distinct_by(_.Name) + |> _long_count(_.Price > 150)) // (identical SQL; result type widens to int64) - // Sum of price of the first-encountered Car per brand + // Sum of price of the first-encountered Car per name let total = _sql(db |> select_from(type) - |> _distinct_by(_.Brand) + |> _distinct_by(_.Name) |> _select(_.Price) |> sum()) - // SELECT SUM("Price") FROM (SELECT *, MIN("Id") FROM "Cars" GROUP BY "Brand") AS "t0" + // SELECT SUM("Price") FROM (SELECT *, MIN("Id") FROM "Cars" GROUP BY "Name") AS "t0" "First-encountered" = the row with the minimum ``@sql_primary_key`` value per group. For tables where the PK is monotonic with insertion order (the diff --git a/doc/source/reference/tutorials/sql_35_streaming.rst b/doc/source/reference/tutorials/sql_35_streaming.rst index eccfb2ba7..78075b7b0 100644 --- a/doc/source/reference/tutorials/sql_35_streaming.rst +++ b/doc/source/reference/tutorials/sql_35_streaming.rst @@ -203,3 +203,5 @@ runtime to materialize one scalar / row / array. Full source: :download:`tutorials/sql/35-streaming.das <../../../../tutorials/sql/35-streaming.das>` Previous tutorial: :ref:`tutorial_sql_backup_vacuum` + + Next tutorial: :ref:`tutorial_sql_attach` diff --git a/doc/source/reference/tutorials/sql_36_attach.rst b/doc/source/reference/tutorials/sql_36_attach.rst index 5f3c2a9a8..066d37fb1 100644 --- a/doc/source/reference/tutorials/sql_36_attach.rst +++ b/doc/source/reference/tutorials/sql_36_attach.rst @@ -157,3 +157,5 @@ When NOT to use ATTACH Full source: :download:`tutorials/sql/36-attach.das <../../../../tutorials/sql/36-attach.das>` Previous tutorial: :ref:`tutorial_sql_streaming` + + Next tutorial: :ref:`tutorial_sql_bulk` diff --git a/doc/source/reference/tutorials/sql_37_bulk_operations.rst b/doc/source/reference/tutorials/sql_37_bulk_operations.rst index f7eb14184..68cddcdca 100644 --- a/doc/source/reference/tutorials/sql_37_bulk_operations.rst +++ b/doc/source/reference/tutorials/sql_37_bulk_operations.rst @@ -104,3 +104,5 @@ after, then ``vacuum`` if free-space matters Full source: :download:`tutorials/sql/37-bulk_operations.das <../../../../tutorials/sql/37-bulk_operations.das>` Previous tutorial: :ref:`tutorial_sql_attach` + + Next tutorial: :ref:`tutorial_sql_concurrency` diff --git a/doc/source/reference/tutorials/sql_38_concurrency.rst b/doc/source/reference/tutorials/sql_38_concurrency.rst index 6aac4e597..a750e1259 100644 --- a/doc/source/reference/tutorials/sql_38_concurrency.rst +++ b/doc/source/reference/tutorials/sql_38_concurrency.rst @@ -113,3 +113,5 @@ tutorial teaches. Full source: :download:`tutorials/sql/38-concurrency.das <../../../../tutorials/sql/38-concurrency.das>` Previous tutorial: :ref:`tutorial_sql_bulk` + + Next tutorial: :ref:`tutorial_sql_schema_from` diff --git a/doc/source/reference/tutorials/sql_39_schema_from.rst b/doc/source/reference/tutorials/sql_39_schema_from.rst index d18eb07a7..2cfc90e5b 100644 --- a/doc/source/reference/tutorials/sql_39_schema_from.rst +++ b/doc/source/reference/tutorials/sql_39_schema_from.rst @@ -166,6 +166,8 @@ miscompile. Full source: :download:`tutorials/sql/39-schema_from.das <../../../../tutorials/sql/39-schema_from.das>` + Previous tutorial: :ref:`tutorial_sql_concurrency` + Next tutorial: :ref:`tutorial_sql_fts5` Multi-version ETL: :ref:`tutorial_sql_schema_evolution` diff --git a/doc/source/reference/tutorials/sql_40_fts5.rst b/doc/source/reference/tutorials/sql_40_fts5.rst index 296553405..99afc6b44 100644 --- a/doc/source/reference/tutorials/sql_40_fts5.rst +++ b/doc/source/reference/tutorials/sql_40_fts5.rst @@ -199,3 +199,5 @@ code never sees escape sequences: Full source: :download:`tutorials/sql/40-fts5.das <../../../../tutorials/sql/40-fts5.das>` Previous tutorial: :ref:`tutorial_sql_schema_from` + + Next tutorial: :ref:`tutorial_sql_triggers` diff --git a/doc/source/reference/tutorials/sql_41_triggers.rst b/doc/source/reference/tutorials/sql_41_triggers.rst index 33458dcaa..2b8872c51 100644 --- a/doc/source/reference/tutorials/sql_41_triggers.rst +++ b/doc/source/reference/tutorials/sql_41_triggers.rst @@ -111,3 +111,5 @@ the recursive case explicitly --- otherwise the audit log doubles Full source: :download:`tutorials/sql/41-triggers.das <../../../../tutorials/sql/41-triggers.das>` Previous tutorial: :ref:`tutorial_sql_fts5` + + Next tutorial: :ref:`tutorial_sql_schema_evolution` diff --git a/tutorials/daStrudel/daStrudel_04_time_manipulation.das b/tutorials/daStrudel/daStrudel_04_time_manipulation.das index acc04aa6d..e086646d2 100644 --- a/tutorials/daStrudel/daStrudel_04_time_manipulation.das +++ b/tutorials/daStrudel/daStrudel_04_time_manipulation.das @@ -44,7 +44,7 @@ def play(var pat : Pattern; seconds : float = 4.0; cps : double = 0.5lf) { def example_fast() { print("=== Section 1: fast(2) — pattern plays twice per cycle ===\n") - let pat <- s("c4 e4 g4 c5") |> fast(2.0lf) + let pat <- note("c4 e4 g4 c5", "sine") |> sustain(0.4) |> fast(2.0lf) play(pat, 4.0) } @@ -87,7 +87,7 @@ def example_rev() { def example_hurry() { print("\n=== Section 4: hurry(2) — twice as fast and an octave higher ===\n") - let pat <- s("c3 e3 g3 c4") |> hurry(2.0lf) + let pat <- note("c3 e3 g3 c4", "sine") |> sustain(0.4) |> hurry(2.0lf) play(pat, 4.0) } diff --git a/tutorials/daStrudel/daStrudel_12_synthesis.das b/tutorials/daStrudel/daStrudel_12_synthesis.das index a7bb9cc2d..6331db134 100644 --- a/tutorials/daStrudel/daStrudel_12_synthesis.das +++ b/tutorials/daStrudel/daStrudel_12_synthesis.das @@ -2,8 +2,8 @@ // // This tutorial covers: // - Oscillator sources via s("sine"|"sawtooth"|"square"|"triangle"|"supersaw") -// - Supersaw (seven detuned saws) for thick pad sounds -// - Noise sources ("white", "pink", "brown") +// - Supersaw (five detuned saws) for thick pad sounds +// - Noise sources ("white", "pink") // - FM synthesis with fm() and fmh() (modulation index and harmonicity) // // Run: daslang.exe tutorials/daStrudel/daStrudel_12_synthesis.das @@ -56,7 +56,7 @@ def example_oscillators() { // Section 2 — Supersaw (thick pad sound) // ────────────────────────────────────────────────────────────────────────── // -// The supersaw voice is seven detuned sawtooth oscillators stacked — the +// The supersaw voice is five detuned sawtooth oscillators stacked — the // classic "trance" pad. It is much wider and fatter than a single saw. A // low-pass filter tames the top end for a warm tone. diff --git a/tutorials/daStrudel/daStrudel_17_hrtf_position.das b/tutorials/daStrudel/daStrudel_17_hrtf_position.das index 5a9ddbb68..a3c387f86 100644 --- a/tutorials/daStrudel/daStrudel_17_hrtf_position.das +++ b/tutorials/daStrudel/daStrudel_17_hrtf_position.das @@ -77,7 +77,7 @@ def example_static_positions() { // // The setters accept patterns. Each event samples the modulation pattern // at its onset to get its azimuth. With sine().range(-180, 180).slow(4), -// the source orbits the listener once every 8 cycles (slow(4) on a 0.5cps +// the source orbits the listener once every 4 cycles (slow(4) on a 0.5cps // stream). def example_animated_azimuth() { diff --git a/tutorials/dasOPENAI/01_first_chat.das b/tutorials/dasOPENAI/01_first_chat.das index a90aeb86f..5e16a9a36 100644 --- a/tutorials/dasOPENAI/01_first_chat.das +++ b/tutorials/dasOPENAI/01_first_chat.das @@ -13,8 +13,8 @@ // which real OpenAI-compatible servers reject. // // To talk to a real server instead of the mock, point base_url at it and pass -// the key, e.g.: -// let client = openai_client("https://api.openai.com/v1", get_env("OPENAI_API_KEY")) +// the key, e.g. (requires daslib/fio): +// let client = openai_client("https://api.openai.com/v1", get_env_variable("OPENAI_API_KEY")) // // Run: daslang.exe tutorials/dasOPENAI/01_first_chat.das diff --git a/tutorials/dasStbImage/03_transforms.das b/tutorials/dasStbImage/03_transforms.das index b80e08b87..a9273a08b 100644 --- a/tutorials/dasStbImage/03_transforms.das +++ b/tutorials/dasStbImage/03_transforms.das @@ -57,7 +57,8 @@ def print_img(lbl : string; img : Image) { // ────────────────────────────────────────────────────────────────────────── // // Image.resize(new_w, new_h) returns a new resized Image using the -// default filter (Catmull-Rom). You can also specify a filter explicitly. +// default filter (Catmull-Rom when upscaling, Mitchell when downscaling). +// You can also specify a filter explicitly. def example_resize() { var inscope img <- make_gradient(64, 64) diff --git a/tutorials/integration/c/03_binding_types.c b/tutorials/integration/c/03_binding_types.c index 46c98e996..139651a24 100644 --- a/tutorials/integration/c/03_binding_types.c +++ b/tutorials/integration/c/03_binding_types.c @@ -85,8 +85,8 @@ das_module * register_module_tutorial_c_03(void) { das_module_bind_alias(mod, lib, "IntArray", "1A"); // --- Enumeration --- - // The third argument to das_enumeration_make is the underlying int type: - // 0 = int8, 1 = int (32-bit), 2 = int16. + // The third argument ('ext') is a boolean: non-zero = int64 underlying + // storage, zero = int. We pass 1 here, so Color is int64-backed. das_enumeration * en = das_enumeration_make("Color", "Color", 1); das_enumeration_add_value(en, "red", "Color_red", Color_red); das_enumeration_add_value(en, "green", "Color_green", Color_green); diff --git a/tutorials/language/17_move_copy_clone.das b/tutorials/language/17_move_copy_clone.das index 58a851147..ac9da5ce8 100644 --- a/tutorials/language/17_move_copy_clone.das +++ b/tutorials/language/17_move_copy_clone.das @@ -129,7 +129,7 @@ def main { // string ✓ ✓ ✓ // array ✗ ✓ ✓ // table ✗ ✓ ✓ - // lambda ✗ ✓ ✗ + // lambda ✓* ✓ ✗ (* = copies fat pointer; both vars alias the same capture frame) // iterator ✗ ✓ ✗ // block ✗ ✗ ✗ // POD struct ✓ ✓ ✓ diff --git a/tutorials/language/30_json.das b/tutorials/language/30_json.das index 6218c7ff0..11f26a803 100644 --- a/tutorials/language/30_json.das +++ b/tutorials/language/30_json.das @@ -341,7 +341,7 @@ def tuples_and_variants() { let s = Shape(circle = 5.0) var js_s = JV(s) print("variant: {write_json(js_s)}\n") - // output: variant: {"circle":5} + // output: variant: {"$variant":0,"circle":5} ($variant is the active-field index) } // === Vector types === diff --git a/tutorials/language/57_toml.das b/tutorials/language/57_toml.das new file mode 100644 index 000000000..bab6fb64e --- /dev/null +++ b/tutorials/language/57_toml.das @@ -0,0 +1,276 @@ +// Tutorial 57: TOML +// +// This tutorial covers: +// - Parsing TOML 1.0 documents with read_toml +// - The TOML -> JsValue type mapping (tables, arrays, scalars, datetimes) +// - Querying the result with json_boost accessors: ?., ?[], ?? +// - Nested tables and dotted keys +// - Arrays and arrays-of-tables +// - Inline tables +// - Integers in decimal / hex / octal / binary +// - Floats including inf and nan +// - Date-times preserved as RFC-3339 strings +// - Error handling for malformed input +// - Deserializing straight into a struct with from_JV +// +// requires: daslib/toml, daslib/json_boost +// Run: daslang.exe tutorials/language/57_toml.das + +options gen2 + +require daslib/toml +require daslib/json_boost + +// === Parsing basics === +// read_toml takes a TOML string and an out error string, and returns a +// JsonValue? — the SAME tree shape daslib/json produces. A TOML document +// is always a table, so a successful parse is always an _object. + +def parsing_basics() { + print("=== Parsing basics ===\n") + + var error : string + var doc = read_toml("title = \"daslang\" +version = 3 +stable = true", error) + + if (doc == null) { + print("parse error: {error}\n") + return + } + print("is object: {doc is _object}\n") + // output: is object: true + + // Top-level keys are accessed exactly like JSON, via json_boost's ?. and ?? + print("title: {doc?.title ?? "?"}\n") + // output: title: daslang + print("version: {doc?.version ?? 0}\n") + // output: version: 3 + print("stable: {doc?.stable ?? false}\n") + // output: stable: true +} + +// === Type mapping === +// Each TOML value maps to one JsValue case: +// table / inline table -> _object +// array / array-of-tables -> _array +// string (all four forms) -> _string (already unescaped) +// integer (any base) -> _longint (int64) +// float (incl inf / nan) -> _number (double) +// boolean -> _bool +// date-time / date / time -> _string (RFC-3339 lexical form preserved) + +def type_mapping() { + print("\n=== Type mapping ===\n") + + var error : string + var doc = read_toml("s = \"text\" +i = 42 +f = 3.14 +b = false +when = 2026-06-12T08:30:00Z", error) + + print("s is string : {doc?.s is _string}\n") // true + print("i is longint: {doc?.i is _longint}\n") // true + print("f is number : {doc?.f is _number}\n") // true + print("b is bool : {doc?.b is _bool}\n") // true + print("when is string: {doc?.when is _string}\n") // true — datetime kept as text +} + +// === Nested tables and dotted keys === +// [section] headers and dotted keys both build nested _object tables. +// Reach into them with chained ?. or ?[] just like JSON. + +def nested_tables() { + print("\n=== Nested tables ===\n") + + var error : string + var doc = read_toml("[server] +host = \"localhost\" +port = 8080 + +[server.tls] +enabled = true + +[owner.contact] +email = \"boris@example.com\"", error) + + print("host: {doc?.server?.host ?? "?"}\n") + // output: host: localhost + print("port: {doc?["server"]?["port"] ?? 0}\n") + // output: port: 8080 + print("tls : {doc?.server?.tls?.enabled ?? false}\n") + // output: tls : true + print("mail: {doc?.owner?.contact?.email ?? "?"}\n") + // output: mail: boris@example.com + + // Missing paths fall through to the default — never a crash. + print("missing: {doc?.server?.timeout ?? -1}\n") + // output: missing: -1 +} + +// === Arrays and arrays-of-tables === +// A bare array becomes an _array of scalars. [[name]] repeated builds an +// _array of _object tables — one element per [[name]] block. + +def arrays() { + print("\n=== Arrays ===\n") + + var error : string + var doc = read_toml("ports = [8000, 8001, 8002] + +[[user]] +name = \"alice\" +admin = true + +[[user]] +name = \"bob\" +admin = false", error) + + // Scalar array — index with ?[] + print("ports[1]: {doc?.ports?[1] ?? 0}\n") + // output: ports[1]: 8001 + + // Array-of-tables — index, then field + print("user[0].name : {doc?.user?[0]?.name ?? "?"}\n") + // output: user[0].name : alice + print("user[1].admin: {doc?.user?[1]?.admin ?? true}\n") + // output: user[1].admin: false + + // Walk the array of tables + let users = doc?.user + if (users != null && users is _array) { + for (u in users as _array) { + print(" - {u?.name ?? "?"} (admin={u?.admin ?? false})\n") + } + } +} + +// === Inline tables === +// { k = v, ... } on one line is an inline table — also an _object. + +def inline_tables() { + print("\n=== Inline tables ===\n") + + var error : string + var doc = read_toml("point = \{ x = 1, y = 2 \} +origin = \{ pos = \{ x = 0, y = 0 \}, tag = \"O\" \}", error) + + print("point.x: {doc?.point?.x ?? 0}\n") + // output: point.x: 1 + print("origin.tag: {doc?.origin?.tag ?? "?"}\n") + // output: origin.tag: O + print("origin.pos.y: {doc?.origin?.pos?.y ?? -1}\n") + // output: origin.pos.y: 0 +} + +// === Integer bases === +// TOML accepts decimal, 0x hex, 0o octal, and 0b binary integers, plus +// underscore digit separators. All decode to the same _longint value. + +def integer_bases() { + print("\n=== Integer bases ===\n") + + var error : string + var doc = read_toml("dec = 1_000 +hex = 0xFF +oct = 0o755 +bin = 0b1010", error) + + print("dec: {doc?.dec ?? 0}\n") // 1000 + print("hex: {doc?.hex ?? 0}\n") // 255 + print("oct: {doc?.oct ?? 0}\n") // 493 + print("bin: {doc?.bin ?? 0}\n") // 10 +} + +// === Floats including inf / nan === +// Exponents, +/-inf and nan are all valid TOML floats -> _number. + +def floats_special() { + print("\n=== Floats ===\n") + + var error : string + var doc = read_toml("avogadro = 6.022e23 +neg = -2.5 +huge = inf +nope = nan", error) + + print("avogadro: {doc?.avogadro ?? 0.0}\n") + print("neg: {doc?.neg ?? 0.0}\n") + print("huge is number: {doc?.huge is _number}\n") // true + print("nope is number: {doc?.nope is _number}\n") // true +} + +// === Date-times === +// JSON has no native date type, so TOML offset-datetime, local-datetime, +// local-date and local-time are preserved verbatim as _string. You keep +// the exact lexical form the document used. + +def datetimes() { + print("\n=== Date-times ===\n") + + var error : string + var doc = read_toml("created = 2026-06-12T08:30:00Z +day = 2026-06-12 +clock = 08:30:00", error) + + print("created: {doc?.created ?? "?"}\n") + // output: created: 2026-06-12T08:30:00Z + print("day: {doc?.day ?? "?"}\n") + // output: day: 2026-06-12 + print("clock: {doc?.clock ?? "?"}\n") + // output: clock: 08:30:00 +} + +// === Error handling === +// read_toml is fail-fast: any malformed input returns null and fills the +// error out-param with a descriptive message — same contract as read_json. + +def error_handling() { + print("\n=== Error handling ===\n") + + var error : string + var doc = read_toml("key = = broken", error) + print("doc is null: {doc == null}\n") + // output: doc is null: true + print("error non-empty: {!empty(error)}\n") + // output: error non-empty: true +} + +// === Deserializing into a struct === +// Because read_toml yields the same JsonValue? tree as JSON, every +// json_boost helper works on it — including from_JV, which fills a struct +// by compile-time reflection. This turns a config file into typed data. + +struct ServerConfig { + host : string + port : int + workers : int +} + +def struct_deserialize() { + print("\n=== Deserialize into a struct ===\n") + + var error : string + var doc = read_toml("host = \"0.0.0.0\" +port = 9090 +workers = 4", error) + + let cfg = from_JV(doc, type) + print("host={cfg.host} port={cfg.port} workers={cfg.workers}\n") + // output: host=0.0.0.0 port=9090 workers=4 +} + +[export] +def main() { + parsing_basics() + type_mapping() + nested_tables() + arrays() + inline_tables() + integer_bases() + floats_special() + datetimes() + error_handling() + struct_deserialize() +} diff --git a/tutorials/language/58_logger.das b/tutorials/language/58_logger.das new file mode 100644 index 000000000..9c032fdd9 --- /dev/null +++ b/tutorials/language/58_logger.das @@ -0,0 +1,154 @@ +// Tutorial 58: Logger +// +// This tutorial covers: +// - Structured logging with daslib/logger (one JSON object per line) +// - Configuring the log file: logger_set_path +// - Level helpers: logger_info / warning / error / debug +// - Attaching a structured fields object to a record +// - Global level filtering: logger_set_min_level +// - Per-category overrides with dotted-prefix matching +// - The print/to_log diversion hook for stdio-transport tools +// +// daslib/logger writes "JSON Lines" (ndjson): one self-contained JSON +// object per line, carrying an ISO-8601 UTC timestamp, level, category, +// message, and an optional fields object. It is built for tool back-ends +// (MCP servers, language servers) where stdout is a wire protocol and a +// stray print() would corrupt the stream. +// +// requires: daslib/logger, daslib/json_boost +// Run: daslang.exe tutorials/language/58_logger.das + +options gen2 + +require daslib/logger +require daslib/json_boost + +let LOG_PATH = "logger_tutorial_demo.log" + +// Close the current handle and delete the file so each section starts from +// an empty log — the next logger_* call lazily reopens it. +def reset_log() { + logger_close() + remove_result(LOG_PATH) +} + +def show_log(title : string) { + print("--- {title} ---\n") + print("{fread(LOG_PATH)}") +} + +// === Basic logging === +// Point the logger at a file, then call the level helpers. Each call is +// (category, message). Categories are free-form dotted strings. + +def basic_logging() { + print("=== Basic logging ===\n") + logger_set_path(LOG_PATH) + reset_log() + + logger_info("app", "starting up") + logger_warning("app.net", "connection retry") + logger_error("app.db", "query failed") + + show_log("file contents") + // Each line is a complete JSON object: + // {"ts":"...","level":"info","cat":"app","msg":"starting up"} +} + +// === Structured fields === +// Pass a JsonValue? as the optional 4th argument to attach machine-readable +// context. The json_boost named-tuple form JV((k=v, ...)) is the shortest +// way to build that object. + +def structured_fields() { + print("\n=== Structured fields ===\n") + reset_log() + + logger_info("app.req", "handled request", + JV((method = "GET", path = "/users", status = 200, ms = 12))) + + show_log("with fields") + // {"ts":"...","level":"info","cat":"app.req","msg":"handled request", + // "fields":{"method":"GET","path":"/users","status":200,"ms":12}} +} + +// === Global level filtering === +// Records below the minimum level are dropped before formatting. The +// default minimum is LOG_INFO, so logger_debug is silent unless you lower +// it. Raise the bar to LOG_WARNING and info-level records disappear. + +def level_filtering() { + print("\n=== Level filtering ===\n") + reset_log() + + logger_set_min_level(LOG_WARNING) + logger_info("app", "info is now below the bar — dropped") + logger_warning("app", "warning is kept") + logger_error("app", "error is kept") + + show_log("only warning + error") + logger_set_min_level(LOG_INFO) // restore default for later sections +} + +// === Per-category overrides === +// logger_set_category_level lifts (or lowers) the bar for one category and +// everything beneath it in the dotted hierarchy. An override on "app.trace" +// also governs "app.trace.sql", "app.trace.http", etc. Anything outside +// that prefix still uses the global minimum. + +def category_levels() { + print("\n=== Per-category overrides ===\n") + reset_log() + + logger_set_category_level("app.trace", LOG_DEBUG) + logger_debug("app.trace.sql", "kept — under the app.trace override") + logger_debug("app.other", "dropped — global minimum is still INFO") + logger_info("app.other", "kept — info meets the global minimum") + + show_log("category override in effect") + logger_clear_category_levels() +} + +// === The diversion hook (advanced) === +// logger_install_hook plugs a global debug agent into the runtime so that +// print() and to_log() — from this context or any future thread — are +// redirected into the log file instead of stdout/stderr. This is the whole +// point of the module for stdio-transport servers. +// +// print() arrives at the hook as a DEBUG-level record, so we lower the +// minimum to LOG_DEBUG first — otherwise the default LOG_INFO bar would +// drop it. to_log carries whatever level you pass. +// +// Note the consequence: once the hook is installed, print() no longer +// reaches the terminal. To DISPLAY the captured lines we write straight to +// the stdout FILE with fwrite, which bypasses the hook — and in doing so +// proves the print() above really was diverted. + +def diversion_hook() { + print("\n=== Diversion hook ===\n") + reset_log() + logger_set_min_level(LOG_DEBUG) + + logger_install_hook() + print("this print is now diverted into the log file\n") + to_log(LOG_WARNING, "this to_log is diverted too\n") + logger_flush() + + let captured = fread(LOG_PATH) + fwrite(fstdout(), "--- captured (shown via fwrite, which is not hooked) ---\n") + fwrite(fstdout(), captured) +} + +[export] +def main() { + basic_logging() + structured_fields() + level_filtering() + category_levels() + diversion_hook() + + // Clean up the demo log file. + logger_close() + remove_result(LOG_PATH) + fwrite(fstdout(), "\ndone\n") +} diff --git a/tutorials/macros/19_add_module_option.das b/tutorials/macros/19_add_module_option.das new file mode 100644 index 000000000..d3532215d --- /dev/null +++ b/tutorials/macros/19_add_module_option.das @@ -0,0 +1,41 @@ +options gen2 + +// Tutorial 19 — Custom module options (add_module_option) +// +// Most `options` are built into the compiler (gen2, persistent_heap, rtti, +// ...). A module can also define its OWN option so that files requiring it +// may set it. The mechanism is add_module_option: +// +// - The module registers an option name + type from a macro_function +// (see add_module_option_mod.das, Section 1). After that, the parser +// accepts `options = ...` in any file that requires the module. +// - A pass macro in the module reads the flag with find_arg and changes +// behavior accordingly (Section 2 of the module). +// +// Here we require add_module_option_mod and switch on its `trace_compile` +// flag. Its lint pass then prints a compile-time note for each module it +// sees. Flip the option to false (or delete the line) and the notes vanish +// — the program still compiles, because the option name stays registered. +// +// require: tutorials/macros/add_module_option_mod.das +// Run: daslang.exe tutorials/macros/19_add_module_option.das + +require add_module_option_mod +options trace_compile = true + +def greet(name : string) { + print("Hello, {name}!\n") +} + +def add(a, b : int) : int => a + b + +[export] +def main() { + greet("world") + print("2 + 3 = {add(2, 3)}\n") +} + +// Expected output (the [trace_compile] line is emitted at COMPILE time): +// [trace_compile] module '
' — 3 function(s) +// Hello, world! +// 2 + 3 = 5 diff --git a/tutorials/macros/add_module_option_mod.das b/tutorials/macros/add_module_option_mod.das new file mode 100644 index 000000000..851a1513a --- /dev/null +++ b/tutorials/macros/add_module_option_mod.das @@ -0,0 +1,59 @@ +options gen2 +options no_aot + +// Tutorial macro module: custom module options via add_module_option. +// +// A module can register its own `options` flag so that any file requiring +// it may write `options = ` without the parser rejecting it +// as unknown. Macros in the module then read that flag to change behavior — +// this is the mechanism behind built-in module options like `shader_like` +// in daslib/validate_code. +// +// Two pieces: +// 1. register_options — a macro_function that calls add_module_option +// while this module's macros compile, declaring the option name + type. +// 2. TraceCompileLint — a lint pass that reads the flag off the program +// being compiled and, when set, prints a per-module compile-time note. + +module add_module_option_mod + +require daslib/ast +require daslib/ast_boost + +// ---- Section 1: register the option ---------------------------------------- +// +// add_module_option(module, name, type) records an option name and its type +// on a module. It must run while THIS module's macros are being compiled, +// hence the is_compiling_macros_in_module guard. `Type.tBool` declares the +// option as boolean; `Type.tInt` / `Type.tString` register other shapes. + +[_macro, macro_function] +def register_options { + if (is_compiling_macros_in_module("add_module_option_mod")) { + this_module() |> add_module_option("trace_compile", Type.tBool) + } +} + +// ---- Section 2: read the option from a lint pass --------------------------- +// +// A [lint_macro] runs once per module compiled after this one. It reads the +// flag off the program options with find_arg; `?as tBool` extracts the bool +// and `?? false` supplies the default when the option was never set. When +// the flag is on, it prints the module name and its function count — at +// compile time, so the note appears before the program's own output. + +[lint_macro] +class TraceCompileLint : AstPassMacro { + def override apply(prog : ProgramPtr; mod : Module?) : bool { + let on = prog._options |> find_arg("trace_compile") ?as tBool ?? false + if (!on) return false + let cm = compiling_module() + var nfun = 0 + cm |> for_each_function("") $(var _func : FunctionPtr) { + nfun++ + } + let name = empty(cm.name) ? "
" : string(cm.name) + print("[trace_compile] module '{name}' — {nfun} function(s)\n") + return false // lint passes never modify the AST + } +}