From e2ea107db6a6f22e01cade8bf80f2520aa88c1b4 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Tue, 6 Jun 2023 18:03:33 +0200
Subject: [PATCH] VVS: Dump snapshot of VVS V4

---
 src/Rust/Cargo.lock                           |  158 +--
 src/Rust/Cargo.toml                           |   12 +-
 src/Rust/README.md                            |   19 +-
 src/Rust/vvs_ass/Cargo.toml                   |    3 +-
 src/Rust/vvs_ass/src/colors.rs                |   42 +-
 src/Rust/vvs_ass/src/definitions.rs           |   34 +-
 src/Rust/vvs_ass/src/drawing.rs               |   29 +-
 src/Rust/vvs_ass/src/elements/line.rs         |   78 +-
 src/Rust/vvs_ass/src/elements/lines.rs        |   14 -
 src/Rust/vvs_ass/src/elements/mod.rs          |   24 +-
 src/Rust/vvs_ass/src/elements/syllabe.rs      |   76 +-
 src/Rust/vvs_ass/src/elements/syllabes.rs     |   14 -
 src/Rust/vvs_ass/src/lib.rs                   |   17 +-
 src/Rust/vvs_ass/src/position.rs              |   19 +-
 src/Rust/vvs_ass/src/reader/ass.rs            |  297 +++--
 src/Rust/vvs_ass/src/reader/json.rs           |    7 +-
 src/Rust/vvs_ass/src/reader/mod.rs            |   40 +-
 src/Rust/vvs_ass/src/styles.rs                |    4 +-
 src/Rust/vvs_ass/src/tests.rs                 |    9 +
 src/Rust/vvs_ass/src/types.rs                 |   55 +-
 src/Rust/vvs_ass/src/values.rs                |  102 +-
 src/Rust/vvs_ass/utils/empty.ass              |   19 +
 src/Rust/vvs_cli/Cargo.toml                   |    3 +-
 src/Rust/vvs_cli/src/args.rs                  |   27 +-
 src/Rust/vvs_cli/src/config.rs                |  115 ++
 src/Rust/vvs_cli/src/lib.rs                   |    3 +
 src/Rust/vvs_cli/src/logger.rs                |   51 +-
 src/Rust/vvs_cli/src/main.rs                  |   58 +-
 src/Rust/vvs_cli/src/parser.rs                |   10 +-
 src/Rust/vvs_font/Cargo.toml                  |    1 -
 src/Rust/vvs_font/build.rs                    |    3 +-
 .../{OFL.txt => NotoSans-LICENCE-OFL.txt}     |    0
 src/Rust/vvs_font/src/font.rs                 |   22 +-
 src/Rust/vvs_font/src/lib.rs                  |    2 +
 src/Rust/vvs_font/src/rect.rs                 |   10 +-
 src/Rust/vvs_lua/Cargo.toml                   |    6 +-
 src/Rust/vvs_lua/build.rs                     |    7 +-
 src/Rust/vvs_lua/src/data/actions.rs          |   10 +-
 src/Rust/vvs_lua/src/data/register.rs         |   83 +-
 src/Rust/vvs_lua/src/dsl.rs                   |   26 +-
 src/Rust/vvs_lua/src/func/actions.rs          |   25 +-
 src/Rust/vvs_lua/src/func/mod.rs              |    1 +
 src/Rust/vvs_lua/src/func/register.rs         |   54 +-
 src/Rust/vvs_lua/src/functions.rs             |  232 +---
 src/Rust/vvs_lua/src/jobs/actions.rs          |  114 +-
 src/Rust/vvs_lua/src/jobs/mod.rs              |    2 +-
 src/Rust/vvs_lua/src/jobs/register.rs         |   48 +-
 src/Rust/vvs_lua/src/lib.rs                   |    2 +-
 src/Rust/vvs_lua/src/libs/bit.rs              |  114 ++
 src/Rust/vvs_lua/src/libs/hashset.rs          |   15 +-
 src/Rust/vvs_lua/src/libs/mod.rs              |   22 +-
 src/Rust/vvs_lua/src/libs/vivy.rs             |  661 +---------
 src/Rust/vvs_lua/src/libs/vivy/byte_code.rs   |  331 +++++
 src/Rust/vvs_lua/src/libs/vivy/graph.rs       |  171 +++
 src/Rust/vvs_lua/src/libs/vivy/runtime.rs     | 1061 +++++++++++++++++
 src/Rust/vvs_lua/src/lua_wrapper.rs           |  319 +++--
 src/Rust/vvs_lua/src/options/actions.rs       |   17 +-
 src/Rust/vvs_lua/src/options/register.rs      |  112 +-
 src/Rust/vvs_lua/src/toml_option.rs           |   25 +-
 src/Rust/vvs_lua/src/traits.rs                |   29 +-
 src/Rust/vvs_lua/src/types.rs                 |   11 +-
 src/Rust/vvs_lua/src/values.rs                |   53 +-
 src/Rust/vvs_procmacro/src/lib.rs             |   44 +-
 src/Rust/vvs_repl/Cargo.toml                  |    3 +-
 src/Rust/vvs_repl/src/error.rs                |    5 +-
 src/Rust/vvs_repl/src/lib.rs                  |   48 +-
 src/Rust/vvs_repl/src/tables.rs               |    6 +-
 src/Rust/vvs_utils/Cargo.toml                 |    5 +
 src/Rust/vvs_utils/src/lib.rs                 |    4 +
 src/Rust/vvs_utils/src/minmax.rs              |   12 +-
 src/Rust/vvs_utils/src/xdg/config.rs          |  384 ++++++
 src/Rust/vvs_utils/src/xdg/folders.rs         |  256 ++++
 src/Rust/vvs_utils/src/xdg/mod.rs             |   47 +
 src/Rust/vvs_utils/src/xdg/options.rs         |   24 +
 src/Rust/vvs_utils/src/xdg/paths.rs           |  208 ++++
 src/Rust/vvs_utils/src/xdg/tests.rs           |    1 +
 utils/vvs/retime.vvl                          |   23 +-
 utils/vvs/test2.vvs                           |    1 +
 78 files changed, 4187 insertions(+), 1812 deletions(-)
 delete mode 100644 src/Rust/vvs_ass/src/elements/lines.rs
 delete mode 100644 src/Rust/vvs_ass/src/elements/syllabes.rs
 create mode 100644 src/Rust/vvs_ass/utils/empty.ass
 create mode 100644 src/Rust/vvs_cli/src/config.rs
 rename src/Rust/vvs_font/fonts/{OFL.txt => NotoSans-LICENCE-OFL.txt} (100%)
 create mode 100644 src/Rust/vvs_lua/src/libs/bit.rs
 create mode 100644 src/Rust/vvs_lua/src/libs/vivy/byte_code.rs
 create mode 100644 src/Rust/vvs_lua/src/libs/vivy/graph.rs
 create mode 100644 src/Rust/vvs_lua/src/libs/vivy/runtime.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/config.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/folders.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/mod.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/options.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/paths.rs
 create mode 100644 src/Rust/vvs_utils/src/xdg/tests.rs

diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock
index 4b777861..b7f96ed7 100644
--- a/src/Rust/Cargo.lock
+++ b/src/Rust/Cargo.lock
@@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
 
 [[package]]
 name = "aho-corasick"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
 dependencies = [
  "memchr",
 ]
@@ -74,9 +74,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "clap"
-version = "4.2.7"
+version = "4.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
+checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -85,9 +85,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.2.7"
+version = "4.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
+checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980"
 dependencies = [
  "anstyle",
  "bitflags",
@@ -98,36 +98,36 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.2.1"
+version = "4.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a19591b2ab0e3c04b588a0e04ddde7b9eaa423646d1b4a8092879216bf47473"
+checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b"
 dependencies = [
  "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.2.0"
+version = "4.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
+checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.18",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.4.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
 
 [[package]]
 name = "clap_mangen"
-version = "0.2.10"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4237e29de9c6949982ba87d51709204504fb8ed2fd38232fcb1e5bf7d4ba48c8"
+checksum = "8f2e32b579dae093c2424a8b7e2bea09c89da01e1ce5065eb2f0a6f1cc15cc1f"
 dependencies = [
  "clap",
  "roff",
@@ -211,9 +211,9 @@ dependencies = [
 
 [[package]]
 name = "io-lifetimes"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
 dependencies = [
  "hermit-abi",
  "libc",
@@ -229,38 +229,29 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
 [[package]]
 name = "libc"
-version = "0.2.142"
+version = "0.2.145"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.3.6"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
 
 [[package]]
 name = "lua-src"
-version = "544.0.1"
+version = "546.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c"
+checksum = "8cb00c1380f1b4b4928dd211c07301ffa34872a239e590bd3219d9e5b213face"
 dependencies = [
  "cc",
 ]
@@ -282,9 +273,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
 name = "mlua"
-version = "0.8.8"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea8ce6788556a67d90567809c7de94dfef2ff1f47ff897aeee935bcfbcdf5735"
+checksum = "07366ed2cd22a3b000aed076e2b68896fb46f06f1f5786c5962da73c0af01577"
 dependencies = [
  "bstr",
  "cc",
@@ -335,9 +326,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "owned_ttf_parser"
@@ -356,9 +347,9 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
 [[package]]
 name = "proc-macro-error"
@@ -386,27 +377,27 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "regex"
-version = "1.8.1"
+version = "1.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
+checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -415,9 +406,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
+checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
 
 [[package]]
 name = "roff"
@@ -433,9 +424,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustix"
-version = "0.37.18"
+version = "0.37.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433"
+checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
 dependencies = [
  "bitflags",
  "errno",
@@ -468,9 +459,9 @@ dependencies = [
 
 [[package]]
 name = "scc"
-version = "1.6.3"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a242e0a9cf55e2fd90e82409ae16c20e45d8c33d1be61cc3d2fe68de0f9ca128"
+checksum = "5d9bf5e8953149d84e5bbcdbc48841b8a2fcf9790b6b5da09fe45a166e3bcc17"
 
 [[package]]
 name = "scopeguard"
@@ -480,15 +471,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "serde"
-version = "1.0.160"
+version = "1.0.163"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.163"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.1"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
+checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
 dependencies = [
  "serde",
 ]
@@ -524,9 +529,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.15"
+version = "2.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -560,14 +565,14 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.18",
 ]
 
 [[package]]
 name = "toml"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
+checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
 dependencies = [
  "indexmap",
  "serde",
@@ -578,18 +583,18 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.1"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
+checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.19.8"
+version = "0.19.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
+checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
 dependencies = [
  "indexmap",
  "serde",
@@ -606,9 +611,9 @@ checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
 
 [[package]]
 name = "unicode-segmentation"
@@ -639,10 +644,11 @@ name = "vvs_ass"
 version = "0.4.0"
 dependencies = [
  "anyhow",
- "lazy_static",
  "log",
  "scc",
+ "serde",
  "thiserror",
+ "unicode-segmentation",
  "vvs_font",
  "vvs_procmacro",
  "vvs_utils",
@@ -656,10 +662,11 @@ dependencies = [
  "clap",
  "clap_complete",
  "clap_mangen",
- "lazy_static",
  "log",
  "scc",
+ "serde",
  "thiserror",
+ "toml",
  "vvs_font",
  "vvs_lua",
  "vvs_repl",
@@ -671,7 +678,6 @@ name = "vvs_font"
 version = "0.4.0"
 dependencies = [
  "ab_glyph",
- "lazy_static",
  "log",
  "thiserror",
  "ttf-parser",
@@ -684,8 +690,11 @@ dependencies = [
  "log",
  "mlua",
  "paste",
+ "scc",
+ "serde",
  "thiserror",
  "toml",
+ "unicode-segmentation",
  "vvs_ass",
  "vvs_utils",
 ]
@@ -696,7 +705,7 @@ version = "0.4.0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.18",
 ]
 
 [[package]]
@@ -704,14 +713,19 @@ name = "vvs_repl"
 version = "0.4.0"
 dependencies = [
  "log",
+ "mlua",
  "rustyline",
  "thiserror",
- "vvs_lua",
 ]
 
 [[package]]
 name = "vvs_utils"
 version = "0.4.0"
+dependencies = [
+ "log",
+ "serde",
+ "thiserror",
+]
 
 [[package]]
 name = "winapi"
diff --git a/src/Rust/Cargo.toml b/src/Rust/Cargo.toml
index ee090e74..43f31b29 100644
--- a/src/Rust/Cargo.toml
+++ b/src/Rust/Cargo.toml
@@ -18,13 +18,13 @@ edition = "2021"
 license = "MIT"
 
 [workspace.dependencies]
-lazy_static = "^1"
 thiserror = "^1"
 anyhow = "^1"
 paste = "^1"
 log = "^0.4"
 
 scc = "^1.3"
+unicode-segmentation = "^1"
 
 toml = { version = "^0.7", features = ["preserve_order"] }
 serde = { version = "^1", default-features = false, features = [
@@ -32,12 +32,20 @@ serde = { version = "^1", default-features = false, features = [
     "derive",
 ] }
 
+mlua = { version = "^0.8", features = [
+    "luajit52",
+    "vendored",
+    "macros",
+    "send",
+] }
+
 [profile.release]
 strip = true
 debug = false
 lto = true
-opt-level = "s"
+opt-level = "z"
 codegen-units = 1
+panic = "abort"
 
 [profile.dev]
 debug = true
diff --git a/src/Rust/README.md b/src/Rust/README.md
index 766bbd0f..da141097 100644
--- a/src/Rust/README.md
+++ b/src/Rust/README.md
@@ -15,6 +15,8 @@ to `vvcc` by `cargo run --bin vvcc --`.
 
 # Misc
 
+## Manpage
+
 To get the `vvcc` manpage, just run:
 
     vvcc --manpage | man -l -
@@ -23,6 +25,9 @@ To install it, you may run the following commands:
 
     mkdir -p $HOME/.local/share/man/man1/
     vvcc --manpage > $HOME/.local/share/man/man1/vvcc.1
+    man vvcc
+
+## Shell completion
 
 To get the completion scripts to source you can use the following commands. You
 can then source those files to get the completion working with your shell.
@@ -41,7 +46,19 @@ To visualize the dependency graph of VivyScript, use the command:
     cargo install cargo-depgraph
     cargo depgraph --build-deps --dedup-transitive-deps | dot -Tpdf | zathura -
 
+## To test the project
+
+The unit-tests can be ran on the project by running `cargo test`. The
+integration tests and the unit-tests can be run together by using pcvs in the
+[tests](tests) folder:
+
+    pip install pcvs        -- Install PCVS
+    (cd tests && pcvs run)  -- Run in the correct folder
+
+You can also install PCVS by cloning the project and running `pip install .`
+from its root.
+
 # Licence
 
 - The VVS project is under the MIT licence
-- The NotoSans fonts are distributed using the [OFL licence](utils/fonts/OFL.txt)
+- The NotoSans fonts are distributed using the [OFL licence](vvs_font/fonts/NotoSans-LICENCE-OFL.txt)
diff --git a/src/Rust/vvs_ass/Cargo.toml b/src/Rust/vvs_ass/Cargo.toml
index 95e6f25c..a066523a 100644
--- a/src/Rust/vvs_ass/Cargo.toml
+++ b/src/Rust/vvs_ass/Cargo.toml
@@ -11,8 +11,9 @@ vvs_procmacro = { path = "../vvs_procmacro" }
 vvs_utils = { path = "../vvs_utils" }
 vvs_font = { path = "../vvs_font" }
 
-lazy_static.workspace = true
+unicode-segmentation.workspace = true
 thiserror.workspace = true
 anyhow.workspace = true
+serde.workspace = true
 log.workspace = true
 scc.workspace = true
diff --git a/src/Rust/vvs_ass/src/colors.rs b/src/Rust/vvs_ass/src/colors.rs
index 71bfd019..380a9a1a 100644
--- a/src/Rust/vvs_ass/src/colors.rs
+++ b/src/Rust/vvs_ass/src/colors.rs
@@ -13,12 +13,7 @@ pub enum ASSColor {
 
 macro_rules! rgb {
     ($NAME: ident => $r: literal $g: literal $b: literal) => {
-        pub const $NAME: ASSColor = ASSColor::RGBA {
-            r: $r,
-            g: $g,
-            b: $b,
-            a: 0,
-        };
+        pub const $NAME: ASSColor = ASSColor::RGBA { r: $r, g: $g, b: $b, a: 0 };
     };
 }
 
@@ -44,6 +39,7 @@ impl ASSColor {
     fn skip_delimiters(str: &str) -> &str {
         str.trim()
             .trim_start_matches('#')
+            .trim_start_matches("&H")
             .trim_start_matches('&')
             .trim_end_matches('&')
     }
@@ -51,22 +47,15 @@ impl ASSColor {
     pub fn try_from_rgba(str: impl AsRef<str>) -> Result<Self, String> {
         let color = Self::skip_delimiters(str.as_ref());
         if !(color.len() == 6 || color.len() == 8) {
-            return Err(format!(
-                "invalid color string description: {}",
-                str.as_ref()
-            ));
+            return Err(format!("invalid color string description: {}", str.as_ref()));
         }
         Ok(Self::RGBA {
-            r: u8::from_str_radix(&color[0..2], 16)
-                .map_err(|err| format!("invalid red description: {err}"))?,
-            g: u8::from_str_radix(&color[2..4], 16)
-                .map_err(|err| format!("invalid green description: {err}"))?,
-            b: u8::from_str_radix(&color[4..6], 16)
-                .map_err(|err| format!("invalid blue description: {err}"))?,
+            r: u8::from_str_radix(&color[0..2], 16).map_err(|err| format!("invalid red description: {err}"))?,
+            g: u8::from_str_radix(&color[2..4], 16).map_err(|err| format!("invalid green description: {err}"))?,
+            b: u8::from_str_radix(&color[4..6], 16).map_err(|err| format!("invalid blue description: {err}"))?,
             a: (color.len() == 8)
                 .then(|| {
-                    u8::from_str_radix(&color[6..8], 16)
-                        .map_err(|err| format!("invalid alpha description: {err}"))
+                    u8::from_str_radix(&color[6..8], 16).map_err(|err| format!("invalid alpha description: {err}"))
                 })
                 .unwrap_or(Ok(0))?,
         })
@@ -75,22 +64,15 @@ impl ASSColor {
     pub fn try_from_bgra(str: impl AsRef<str>) -> Result<Self, String> {
         let color = Self::skip_delimiters(str.as_ref());
         if !(color.len() == 6 || color.len() == 8) {
-            return Err(format!(
-                "invalid color string description: {}",
-                str.as_ref()
-            ));
+            return Err(format!("invalid color string description: {}", str.as_ref()));
         }
         Ok(Self::RGBA {
-            r: u8::from_str_radix(&color[4..6], 16)
-                .map_err(|err| format!("invalid red description: {err}"))?,
-            g: u8::from_str_radix(&color[2..4], 16)
-                .map_err(|err| format!("invalid green description: {err}"))?,
-            b: u8::from_str_radix(&color[0..2], 16)
-                .map_err(|err| format!("invalid blue description: {err}"))?,
+            r: u8::from_str_radix(&color[4..6], 16).map_err(|err| format!("invalid red description: {err}"))?,
+            g: u8::from_str_radix(&color[2..4], 16).map_err(|err| format!("invalid green description: {err}"))?,
+            b: u8::from_str_radix(&color[0..2], 16).map_err(|err| format!("invalid blue description: {err}"))?,
             a: (color.len() == 8)
                 .then(|| {
-                    u8::from_str_radix(&color[6..8], 16)
-                        .map_err(|err| format!("invalid alpha description: {err}"))
+                    u8::from_str_radix(&color[6..8], 16).map_err(|err| format!("invalid alpha description: {err}"))
                 })
                 .unwrap_or(Ok(0))?,
         })
diff --git a/src/Rust/vvs_ass/src/definitions.rs b/src/Rust/vvs_ass/src/definitions.rs
index e8b5689f..dcd1b201 100644
--- a/src/Rust/vvs_ass/src/definitions.rs
+++ b/src/Rust/vvs_ass/src/definitions.rs
@@ -160,6 +160,12 @@ pub enum ScriptInfoKey {
     /// - 2: no word wrapping, \n \N both breaks
     /// - 3: same as 0, but lower line gets wider.
     WrapStyle,
+
+    /// Undocumented, generated by Aegisub.
+    ScaledBorderAndShadow,
+
+    /// Undocumented, generated by Aegisub.
+    YCbCrMatrix,
 }
 
 impl FromStr for ScriptInfoKey {
@@ -169,20 +175,22 @@ impl FromStr for ScriptInfoKey {
         use ScriptInfoKey::*;
         match s.trim() {
             "Title" => Ok(Title),
-            "Original Script" => Ok(OriginalScript),
-            "Original Translation" => Ok(OriginalTranslation),
-            "Original Editing" => Ok(OriginalEditing),
-            "Original Timing" => Ok(OriginalTiming),
-            "Synch Point" => Ok(SynchPoint),
-            "Script Updated By" => Ok(ScriptUpdatedBy),
-            "Update Details" => Ok(UpdateDetails),
-            "Script Type" => Ok(ScriptType),
+            "OriginalTranslation" | "Original Translation" => Ok(OriginalTranslation),
+            "OriginalEditing" | "Original Editing" => Ok(OriginalEditing),
+            "ScriptUpdatedBy" | "Script Updated By" => Ok(ScriptUpdatedBy),
+            "OriginalTiming" | "Original Timing" => Ok(OriginalTiming),
+            "OriginalScript" | "Original Script" => Ok(OriginalScript),
+            "UpdateDetails" | "Update Details" => Ok(UpdateDetails),
+            "YCbCrMatrix" | "YCbCr Matrix" => Ok(YCbCrMatrix),
+            "SynchPoint" | "Synch Point" => Ok(SynchPoint),
+            "ScriptType" | "Script Type" => Ok(ScriptType),
             "Collisions" => Ok(Collisions),
             "PlayResY" => Ok(PlayResY),
             "PlayResX" => Ok(PlayResX),
             "PlayDepth" => Ok(PlayDepth),
             "Timer" => Ok(Timer),
             "WrapStyle" => Ok(WrapStyle),
+            "ScaledBorderAndShadow" => Ok(ScaledBorderAndShadow),
             _ => Err(format!("unknown Script Info key: {s}")),
         }
     }
@@ -194,11 +202,11 @@ impl FromStr for ASSFileSection {
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         use ASSFileSection::*;
         const TRIM_PAT: &[char] = &['[', ']', ' ', '\t'];
-        match s.trim_matches(TRIM_PAT) {
-            "Script Info" => Ok(ScriptInfo),
-            "Events" => Ok(Events),
-            "v4 Styles" | "v4 Styles+" => Ok(V4Styles),
-            _ => Err(format!("unknown section [{s}]")),
+        match s.trim_matches(TRIM_PAT).to_lowercase().as_str() {
+            "script info" => Ok(ScriptInfo),
+            "events" => Ok(Events),
+            "v4+ styles" | "v4 styles" | "v4 styles+" => Ok(V4Styles),
+            s => Err(format!("unknown section [{s}]")),
         }
     }
 }
diff --git a/src/Rust/vvs_ass/src/drawing.rs b/src/Rust/vvs_ass/src/drawing.rs
index a3c9a997..c2d2a882 100644
--- a/src/Rust/vvs_ass/src/drawing.rs
+++ b/src/Rust/vvs_ass/src/drawing.rs
@@ -125,34 +125,11 @@ impl std::fmt::Display for ASSDrawingCmd {
             ASSDrawingCmd::M { x, y } => write!(f, "m {x} {y}"),
             ASSDrawingCmd::N { x, y } => write!(f, "n {x} {y}"),
             ASSDrawingCmd::L { x, y } => write!(f, "l {x} {y}"),
-            ASSDrawingCmd::B {
-                x1,
-                y1,
-                x2,
-                y2,
-                x3,
-                y3,
-            } => write!(f, "b {x1} {y1} {x2} {y2} {x3} {y3}"),
-            ASSDrawingCmd::S {
-                x1,
-                y1,
-                x2,
-                y2,
-                x3,
-                y3,
-                others,
-            } if others.is_empty() => {
+            ASSDrawingCmd::B { x1, y1, x2, y2, x3, y3 } => write!(f, "b {x1} {y1} {x2} {y2} {x3} {y3}"),
+            ASSDrawingCmd::S { x1, y1, x2, y2, x3, y3, others } if others.is_empty() => {
                 write!(f, "b {x1} {y1} {x2} {y2} {x3} {y3}")
             }
-            ASSDrawingCmd::S {
-                x1,
-                y1,
-                x2,
-                y2,
-                x3,
-                y3,
-                others,
-            } => {
+            ASSDrawingCmd::S { x1, y1, x2, y2, x3, y3, others } => {
                 let others = others
                     .iter()
                     .map(|(x, y)| format!("{x} {y}"))
diff --git a/src/Rust/vvs_ass/src/elements/line.rs b/src/Rust/vvs_ass/src/elements/line.rs
index f58e003d..63df87f0 100644
--- a/src/Rust/vvs_ass/src/elements/line.rs
+++ b/src/Rust/vvs_ass/src/elements/line.rs
@@ -1,14 +1,78 @@
-use crate::{elements::syllabes::ASSSyllabesPtr, ASSAuxTablePtr, ASSPositionPtr};
+use crate::{ASSAuxTable, ASSPosition, ASSSyllabePtr};
 
 #[derive(Debug, Default, Clone, PartialEq)]
 pub struct ASSLine {
-    pub position: ASSPositionPtr,
-    pub content: ASSSyllabesPtr,
-    pub aux: ASSAuxTablePtr,
+    pub position: ASSPosition,
+    pub content: Vec<ASSSyllabePtr>,
+    pub aux: ASSAuxTable,
+    pub start: i64,
+    pub fini: i64,
 }
 
-pub type ASSLinePtr = crate::Ptr<ASSLine>;
+#[derive(Debug, Default, Clone)]
+#[repr(transparent)]
+pub struct ASSLinePtr(pub crate::Ptr<ASSLine>);
 
-impl ASSLine {
-    crate::impl_into_ptr! { ASSLinePtr }
+#[derive(Debug, Default, Clone)]
+#[repr(transparent)]
+pub struct ASSLines(pub Vec<ASSLinePtr>);
+
+impl PartialEq for ASSLinePtr {
+    fn eq(&self, other: &Self) -> bool {
+        *self.0.try_read().unwrap() == *other.0.try_read().unwrap()
+    }
+}
+
+impl PartialEq for ASSLines {
+    fn eq(&self, Self(other): &Self) -> bool {
+        let Self(this) = self;
+        (this.len() != other.len()) && {
+            this.iter()
+                .zip(other.iter())
+                .fold(true, |acc, (ASSLinePtr(this), ASSLinePtr(other))| {
+                    acc && (*this.try_read().unwrap() == *other.try_read().unwrap())
+                })
+        }
+    }
+}
+
+impl From<ASSLine> for ASSLinePtr {
+    fn from(value: ASSLine) -> Self {
+        ASSLinePtr(crate::ptr!(value))
+    }
+}
+
+impl ASSLines {
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    pub fn push(&mut self, value: impl Into<ASSLinePtr>) {
+        self.0.push(value.into())
+    }
+}
+
+impl Extend<ASSLinePtr> for ASSLines {
+    fn extend<T: IntoIterator<Item = ASSLinePtr>>(&mut self, iter: T) {
+        self.0.extend(iter)
+    }
+}
+
+impl Extend<ASSLine> for ASSLines {
+    fn extend<T: IntoIterator<Item = ASSLine>>(&mut self, iter: T) {
+        self.extend(iter.into_iter().map(|line| ASSLinePtr(crate::ptr!(line))))
+    }
+}
+
+impl IntoIterator for ASSLines {
+    type Item = ASSLinePtr;
+    type IntoIter = <Vec<ASSLinePtr> as IntoIterator>::IntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
 }
diff --git a/src/Rust/vvs_ass/src/elements/lines.rs b/src/Rust/vvs_ass/src/elements/lines.rs
deleted file mode 100644
index cdd10df9..00000000
--- a/src/Rust/vvs_ass/src/elements/lines.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use crate::{elements::line::ASSLinePtr, ASSAuxTablePtr, ASSPositionPtr};
-
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct ASSLines {
-    pub position: ASSPositionPtr,
-    pub content: Vec<ASSLinePtr>,
-    pub aux: ASSAuxTablePtr,
-}
-
-pub type ASSLinesPtr = crate::Ptr<ASSLines>;
-
-impl ASSLines {
-    crate::impl_into_ptr! { ASSLinesPtr }
-}
diff --git a/src/Rust/vvs_ass/src/elements/mod.rs b/src/Rust/vvs_ass/src/elements/mod.rs
index a411fa4c..2de364b6 100644
--- a/src/Rust/vvs_ass/src/elements/mod.rs
+++ b/src/Rust/vvs_ass/src/elements/mod.rs
@@ -1,35 +1,39 @@
 mod line;
-mod lines;
 mod syllabe;
-mod syllabes;
 
-pub use self::{line::*, lines::*, syllabe::*, syllabes::*};
+pub use self::{line::*, syllabe::*};
 
 use crate::{definitions::ScriptInfoKey, ASSStyle};
 use std::collections::HashMap;
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone)]
 pub struct ASSContainer {
-    pub lines: ASSLinesPtr,
+    pub lines: ASSLines,
     pub script_info: HashMap<ScriptInfoKey, String>,
     pub styles: HashMap<String, ASSStyle>,
 }
 
-pub type ASSContainerPtr = crate::Ptr<ASSContainer>;
+#[derive(Debug, Clone)]
+#[repr(transparent)]
+pub struct ASSContainerPtr(pub crate::Ptr<ASSContainer>);
 
 impl ASSContainer {
-    crate::impl_into_ptr! { ASSContainerPtr }
-
     /// Create an ASS container from its parts, they must be valide!
     pub(crate) fn from_parts(
-        lines: ASSLinesPtr,
+        lines: impl IntoIterator<Item = ASSLine>,
         script_info: HashMap<ScriptInfoKey, String>,
         styles: HashMap<String, ASSStyle>,
     ) -> Self {
         Self {
-            lines,
+            lines: ASSLines(lines.into_iter().map(|line| ASSLinePtr(crate::ptr!(line))).collect()),
             script_info,
             styles,
         }
     }
 }
+
+impl From<ASSContainer> for ASSContainerPtr {
+    fn from(value: ASSContainer) -> Self {
+        Self(crate::ptr!(value))
+    }
+}
diff --git a/src/Rust/vvs_ass/src/elements/syllabe.rs b/src/Rust/vvs_ass/src/elements/syllabe.rs
index 00fa4633..4c098045 100644
--- a/src/Rust/vvs_ass/src/elements/syllabe.rs
+++ b/src/Rust/vvs_ass/src/elements/syllabe.rs
@@ -1,14 +1,78 @@
-use crate::{ASSAuxTablePtr, ASSPositionPtr};
+use crate::{ASSAuxTable, ASSPosition};
 
 #[derive(Debug, Default, Clone, PartialEq)]
 pub struct ASSSyllabe {
-    pub position: ASSPositionPtr,
+    pub position: ASSPosition,
     pub content: String,
-    pub aux: ASSAuxTablePtr,
+    pub aux: ASSAuxTable,
+    pub start: i64,
+    pub fini: i64,
 }
 
-pub type ASSSyllabePtr = crate::Ptr<ASSSyllabe>;
+#[derive(Debug, Default, Clone)]
+#[repr(transparent)]
+pub struct ASSSyllabePtr(pub crate::Ptr<ASSSyllabe>);
 
-impl ASSSyllabe {
-    crate::impl_into_ptr! { ASSSyllabePtr }
+#[derive(Debug, Default, Clone)]
+#[repr(transparent)]
+pub struct ASSSyllabes(pub Vec<ASSSyllabePtr>);
+
+impl PartialEq for ASSSyllabePtr {
+    fn eq(&self, other: &Self) -> bool {
+        *self.0.try_read().unwrap() == *other.0.try_read().unwrap()
+    }
+}
+
+impl PartialEq for ASSSyllabes {
+    fn eq(&self, Self(other): &Self) -> bool {
+        let Self(this) = self;
+        (this.len() != other.len()) && {
+            this.iter()
+                .zip(other.iter())
+                .fold(true, |acc, (ASSSyllabePtr(this), ASSSyllabePtr(other))| {
+                    acc && (*this.try_read().unwrap() == *other.try_read().unwrap())
+                })
+        }
+    }
+}
+
+impl From<ASSSyllabe> for ASSSyllabePtr {
+    fn from(value: ASSSyllabe) -> Self {
+        ASSSyllabePtr(crate::ptr!(value))
+    }
+}
+
+impl ASSSyllabes {
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    pub fn push(&mut self, value: impl Into<ASSSyllabePtr>) {
+        self.0.push(value.into())
+    }
+}
+
+impl Extend<ASSSyllabePtr> for ASSSyllabes {
+    fn extend<T: IntoIterator<Item = ASSSyllabePtr>>(&mut self, iter: T) {
+        self.0.extend(iter)
+    }
+}
+
+impl Extend<ASSSyllabe> for ASSSyllabes {
+    fn extend<T: IntoIterator<Item = ASSSyllabe>>(&mut self, iter: T) {
+        self.extend(iter.into_iter().map(|syllabe| ASSSyllabePtr(crate::ptr!(syllabe))))
+    }
+}
+
+impl IntoIterator for ASSSyllabes {
+    type Item = ASSSyllabePtr;
+    type IntoIter = <Vec<ASSSyllabePtr> as IntoIterator>::IntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
 }
diff --git a/src/Rust/vvs_ass/src/elements/syllabes.rs b/src/Rust/vvs_ass/src/elements/syllabes.rs
deleted file mode 100644
index 8a8143d9..00000000
--- a/src/Rust/vvs_ass/src/elements/syllabes.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use crate::{elements::syllabe::ASSSyllabePtr, ASSAuxTablePtr, ASSPositionPtr};
-
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct ASSSyllabes {
-    pub position: ASSPositionPtr,
-    pub content: Vec<ASSSyllabePtr>,
-    pub aux: ASSAuxTablePtr,
-}
-
-pub type ASSSyllabesPtr = crate::Ptr<ASSSyllabes>;
-
-impl ASSSyllabes {
-    crate::impl_into_ptr! { ASSSyllabesPtr }
-}
diff --git a/src/Rust/vvs_ass/src/lib.rs b/src/Rust/vvs_ass/src/lib.rs
index 0e1a05ae..eba3a485 100644
--- a/src/Rust/vvs_ass/src/lib.rs
+++ b/src/Rust/vvs_ass/src/lib.rs
@@ -1,4 +1,5 @@
 //! ASS objects for Vivy.
+#![forbid(unsafe_code)]
 
 mod colors;
 mod definitions;
@@ -14,23 +15,13 @@ mod values;
 mod tests;
 
 pub use crate::{colors::*, drawing::*, elements::*, position::*, styles::*, types::*, values::*};
-pub use reader::{ass_lines_from_file, ASSElementReaderError};
+pub use reader::{ass_container_from_file, ass_container_from_str, ASSElementReaderError, ContainerFileType};
 
-pub type Ptr<T> = std::rc::Rc<std::cell::RefCell<T>>;
+pub type Ptr<T> = std::sync::Arc<std::sync::RwLock<T>>;
 
 #[macro_export]
 macro_rules! ptr {
     ($expr: expr) => {
-        std::rc::Rc::new(std::cell::RefCell::new($expr))
+        std::sync::Arc::new(std::sync::RwLock::new($expr))
     };
 }
-
-macro_rules! impl_into_ptr {
-    ($PTR: ident) => {
-        /// Wrap the struct into a pointer.
-        pub fn into_ptr(self) -> $PTR {
-            crate::ptr!(self)
-        }
-    };
-}
-pub(crate) use impl_into_ptr;
diff --git a/src/Rust/vvs_ass/src/position.rs b/src/Rust/vvs_ass/src/position.rs
index a75eb9e6..bc335d23 100644
--- a/src/Rust/vvs_ass/src/position.rs
+++ b/src/Rust/vvs_ass/src/position.rs
@@ -13,14 +13,7 @@ pub enum ASSPosition {
     LinearMove { x1: i64, y1: i64, x2: i64, y2: i64 },
 
     /// A linear movement with time step specified.
-    TimedMove {
-        x1: i64,
-        y1: i64,
-        x2: i64,
-        y2: i64,
-        from_ms: i64,
-        to_ms: i64,
-    },
+    TimedMove { x1: i64, y1: i64, x2: i64, y2: i64, from_ms: i64, to_ms: i64 },
 }
 
 /// The alignement of the object.
@@ -55,7 +48,7 @@ pub enum ASSAlign {
 }
 
 /// Pointer used to store a position, to help with mutability with LUA wrappers.
-pub type ASSPositionPtr = crate::Ptr<ASSPosition>;
+pub type ASSPositionPtr = std::sync::Arc<ASSPosition>;
 
 impl std::str::FromStr for ASSAlign {
     type Err = String;
@@ -71,9 +64,7 @@ impl std::str::FromStr for ASSAlign {
             "7" => Ok(ASSAlign::TL),
             "8" => Ok(ASSAlign::TC),
             "9" => Ok(ASSAlign::TR),
-            s => Err(format!(
-                "invalid value for ASSAlign, must be in `1..=9`, got: {s}"
-            )),
+            s => Err(format!("invalid value for ASSAlign, must be in `1..=9`, got: {s}")),
         }
     }
 }
@@ -85,5 +76,7 @@ impl From<vvs_font::Point> for ASSPosition {
 }
 
 impl ASSPosition {
-    crate::impl_into_ptr! { ASSPositionPtr }
+    pub fn into_ptr(self) -> ASSPositionPtr {
+        std::sync::Arc::new(self)
+    }
 }
diff --git a/src/Rust/vvs_ass/src/reader/ass.rs b/src/Rust/vvs_ass/src/reader/ass.rs
index d024c677..c1927c02 100644
--- a/src/Rust/vvs_ass/src/reader/ass.rs
+++ b/src/Rust/vvs_ass/src/reader/ass.rs
@@ -1,23 +1,116 @@
 use crate::{
     definitions::{ASSEvent, ASSFileSection, ScriptInfoKey},
     reader::ASSElementReader,
-    ASSColor, ASSContainer, ASSElementReaderError, ASSLines, ASSStyle,
+    ASSColor, ASSContainer, ASSElementReaderError, ASSLine, ASSStyle,
 };
 use std::collections::HashMap;
+use vvs_procmacro::EnumVariantFromStr;
+
+/// Line fields of the `V4+ Styles` line in the ASS file.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumVariantFromStr)]
+enum V4PlusStyleFields {
+    Name,
+    Fontname,
+    Fontsize,
+    PrimaryColour,
+    SecondaryColour,
+    OutlineColour,
+    BackColour,
+    Bold,
+    Italic,
+    Underline,
+    StrikeOut,
+    ScaleX,
+    ScaleY,
+    Spacing,
+    Angle,
+    BorderStyle,
+    Outline,
+    Shadow,
+    Alignment,
+    MarginL,
+    MarginR,
+    MarginV,
+    Encoding,
+}
+
+/// Line fields of the `V4+ Styles` line in the ASS file.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumVariantFromStr)]
+enum EventFields {
+    Marked,
+    Layer,
+    Start,
+    End,
+    Style,
+    Name,
+    MarginL,
+    MarginR,
+    MarginV,
+    Effect,
+    Text,
+}
+
+/// Helper wrapper struct to auto unwrap things when we get entries from the HashMap.
+#[derive(Debug)]
+struct UnwrapHashMap<'a, K>(HashMap<K, &'a str>)
+where
+    K: Eq + Copy + std::hash::Hash + std::fmt::Debug;
+
+impl<'a, K: Eq + Copy + std::hash::Hash + std::fmt::Debug> UnwrapHashMap<'a, K> {
+    /// Get the element from the hashmap, if the element is not present just panics.
+    pub fn get(&self, key: K) -> &str {
+        self.0.get(&key).unwrap_or_else(|| panic!("failed to find key {key:?}"))
+    }
+
+    /// Try to get the element from the hashmap, if the element is not present, returns the
+    /// specified default value instead.
+    pub fn get_or(&self, key: K, default: &'a str) -> &str {
+        self.0.get(&key).copied().unwrap_or(default)
+    }
+
+    /// Try to get the element from the hashmap, if the element is present then tries to parse it.
+    pub fn parsed<T>(
+        &self,
+        key: K,
+        parser: fn(&str, name: &str) -> Result<T, ASSElementReaderError>,
+        name: &str,
+    ) -> Result<T, ASSElementReaderError> {
+        let Self(hashmap) = self;
+        let element = hashmap.get(&key).ok_or(ASSElementReaderError::Custom(format!(
+            "failed to find entry with key {key:?}"
+        )))?;
+        parser(element, name)
+    }
+
+    /// Try to get the element from the hashmap, if the element is present then tries to parse it.
+    /// If the element is not present or the parsing failed, returns the default value.
+    pub fn parsed_or<T>(
+        &self,
+        key: K,
+        parser: fn(&str, &str) -> Result<T, ASSElementReaderError>,
+        name: &str,
+        default: T,
+    ) -> T {
+        self.parsed(key, parser, name).unwrap_or(default)
+    }
+}
 
 /// Documentation available here: http://www.tcax.org/docs/ass-specs.html or in the `utils/manual`
 /// folder from the root of the vvs project.
 #[derive(Debug, Default)]
 pub struct ASSReader {
     section: Option<ASSFileSection>,
+
+    styles_format: Vec<V4PlusStyleFields>,
+    events_format: Vec<EventFields>,
+
     script_info: HashMap<ScriptInfoKey, String>,
     styles: HashMap<String, ASSStyle>,
     events: Vec<ASSEvent>,
 }
 
 fn parse_color(color: &str, name: &str) -> Result<ASSColor, ASSElementReaderError> {
-    ASSColor::try_from_bgra(color)
-        .map_err(|err| ASSElementReaderError::Custom(format!("invalid {name} color: {err}")))
+    ASSColor::try_from_bgra(color).map_err(|err| ASSElementReaderError::Custom(format!("invalid {name} color: {err}")))
 }
 
 fn parse_boolean(boolean: &str, name: &str) -> Result<bool, ASSElementReaderError> {
@@ -31,34 +124,32 @@ fn parse_boolean(boolean: &str, name: &str) -> Result<bool, ASSElementReaderErro
 }
 
 fn parse_float(float: &str, name: &str) -> Result<f64, ASSElementReaderError> {
-    float.parse::<f64>().map_err(|err| {
-        ASSElementReaderError::Custom(format!("invalid float value for {name}: {err}"))
-    })
+    float
+        .parse::<f64>()
+        .map_err(|err| ASSElementReaderError::Custom(format!("invalid float value for {name}: {err}")))
 }
 
 fn parse_int(int: &str, name: &str) -> Result<i64, ASSElementReaderError> {
-    int.parse::<i64>().map_err(|err| {
-        ASSElementReaderError::Custom(format!("invalid integer value for {name}: {err}"))
-    })
+    int.parse::<i64>()
+        .map_err(|err| ASSElementReaderError::Custom(format!("invalid integer value for {name}: {err}")))
 }
 
 /// Parse dates in the `0:00:00:00` format
 fn parse_date(date: &str, name: &str) -> Result<i64, ASSElementReaderError> {
-    let [h, m, s, c] = &date.split(':').collect::<Vec<_>>()[..] else {
-        return Err(ASSElementReaderError::Custom(format!("invalid date for {name}: {date}")))
+    let std_err = "invalid date for {name}, expected \"h:mm:ss.cc\", got {date:?}".to_string();
+    let [h, m, sc] = &date.split(':').collect::<Vec<_>>()[..] else {
+        return Err(ASSElementReaderError::Custom(std_err))
+    };
+    let [s, c] = &sc.split('.').collect::<Vec<_>>()[..] else {
+        return Err(ASSElementReaderError::Custom(std_err))
     };
     let check_compnent = |str: &str, compnent: &str, len: usize| {
         if str.len() > len {
-            Err(ASSElementReaderError::Custom(format!(
-                "invalid date for {name}: {date}"
-            )))
+            Err(ASSElementReaderError::Custom(std_err.clone()))
         } else {
-            let ret = str.parse::<u16>().map_err(|err| {
-                ASSElementReaderError::Custom(format!(
-                    "invalid component {compnent} for date {name}: {err}"
-                ))
-            })?;
-            Ok(ret as i64)
+            Ok(str.parse::<u16>().map_err(|err| {
+                ASSElementReaderError::Custom(format!("invalid component {compnent} for date {name}: {err}"))
+            })? as i64)
         }
     };
     let (h, m, s, c) = (
@@ -75,12 +166,13 @@ impl ASSReader {
         let Some((key, value)) = line.split_once(':') else {
             return Err(ASSElementReaderError::Custom(format!("invalid script info line: {line}")))
         };
+        let value = value.trim();
         let key = match key
             .trim()
             .parse::<ScriptInfoKey>()
             .map_err(ASSElementReaderError::Custom)?
         {
-            ScriptInfoKey::ScriptType if value.ne("V4.00+") => {
+            ScriptInfoKey::ScriptType if value.ne("V4.00+") && value.ne("v4.00+") => {
                 return Err(ASSElementReaderError::Custom(format!(
                     "invalid value for key '{key:?}' in script info section: {value}"
                 )))
@@ -97,49 +189,72 @@ impl ASSReader {
                 "redefinition of key '{key:?}' in script info section"
             ))),
             None => {
-                self.script_info.insert(key, value.trim().to_string());
+                self.script_info.insert(key, value.to_string());
                 Ok(())
             }
         }
     }
 
     fn read_v4_style(&mut self, line: &str) -> Result<(), ASSElementReaderError> {
-        let fields: Vec<_> = line.trim().split(',').map(|str| str.trim()).collect();
-        let [_, name, font_name, font_size, primary_color, secondary_color, outline_color, back_color, bold, italic, underline, strikeout, scalex, scaley, spacing, angle, border_style, outline, shadow, alignment, marginl, marginr, marginv, encoding] = &fields[..] else {
-            return Err(ASSElementReaderError::Custom(format!("invalid line composed of fields: {fields:#?}")))
+        let line = if line.starts_with("Format:") {
+            let line = line.split_once(':').unwrap().1.trim();
+            self.styles_format = line
+                .split(',')
+                .flat_map(|str| str.trim().parse::<V4PlusStyleFields>())
+                .collect();
+            return Ok(());
+        } else {
+            line.split_once(':').unwrap().1.trim()
         };
 
-        if encoding.ne(&"0") {
+        let fields: Vec<_> = line.trim().split(',').map(|str| str.trim()).collect();
+        if fields.len() != self.styles_format.len() {
             return Err(ASSElementReaderError::Custom(format!(
-                "we expected the encoding '0', got: {encoding}"
+                "style format specified {} fields, got {} in the style description",
+                self.styles_format.len(),
+                fields.len()
             )));
         }
+        let fields: UnwrapHashMap<V4PlusStyleFields> =
+            UnwrapHashMap(HashMap::from_iter(self.styles_format.iter().copied().zip(fields)));
 
+        let encoding = fields.get(V4PlusStyleFields::Encoding);
+        if encoding.ne("1") {
+            return Err(ASSElementReaderError::Custom(format!(
+                "we expected the encoding '1', got: {encoding}"
+            )));
+        }
+
+        let name = fields.get(V4PlusStyleFields::Name);
         let style = ASSStyle {
             name: name.to_string(),
-            font_name: font_name.to_string(),
-            font_size: parse_int(font_size, "font size")?,
-            primary_color: parse_color(primary_color, "primary")?,
-            secondary_color: parse_color(secondary_color, "secondary")?,
-            outline_color: parse_color(outline_color, "outline")?,
-            back_color: parse_color(back_color, "back")?,
-            bold: parse_boolean(bold, "bold")?,
-            italic: parse_boolean(italic, "italic")?,
-            underline: parse_boolean(underline, "underline")?,
-            strikeout: parse_boolean(strikeout, "strikeout")?,
-            scale_x: parse_float(scalex, "scale_x")?,
-            scale_y: parse_float(scaley, "scale_y")?,
-            spacing: parse_float(spacing, "spacing")?,
-            angle: parse_float(angle, "angle")?,
-            border_style: border_style
+            font_name: fields.get(V4PlusStyleFields::Fontname).to_string(),
+            font_size: fields.parsed(V4PlusStyleFields::Fontsize, parse_int, "font size")?,
+            primary_color: fields.parsed(V4PlusStyleFields::PrimaryColour, parse_color, "primary")?,
+            secondary_color: fields.parsed(V4PlusStyleFields::SecondaryColour, parse_color, "secondary")?,
+            outline_color: fields.parsed(V4PlusStyleFields::OutlineColour, parse_color, "outline")?,
+            back_color: fields.parsed(V4PlusStyleFields::BackColour, parse_color, "back")?,
+            bold: fields.parsed(V4PlusStyleFields::Bold, parse_boolean, "bold")?,
+            italic: fields.parsed(V4PlusStyleFields::Italic, parse_boolean, "italic")?,
+            underline: fields.parsed(V4PlusStyleFields::Underline, parse_boolean, "underline")?,
+            strikeout: fields.parsed(V4PlusStyleFields::StrikeOut, parse_boolean, "strikeout")?,
+            scale_x: fields.parsed(V4PlusStyleFields::ScaleX, parse_float, "scale_x")?,
+            scale_y: fields.parsed(V4PlusStyleFields::ScaleY, parse_float, "scale_y")?,
+            spacing: fields.parsed(V4PlusStyleFields::Spacing, parse_float, "spacing")?,
+            angle: fields.parsed(V4PlusStyleFields::Angle, parse_float, "angle")?,
+            border_style: fields
+                .get(V4PlusStyleFields::BorderStyle)
+                .parse()
+                .map_err(ASSElementReaderError::Custom)?,
+            outline: fields.parsed(V4PlusStyleFields::Outline, parse_float, "outline")?,
+            shadow: fields.parsed(V4PlusStyleFields::Shadow, parse_float, "shadow")?,
+            alignment: fields
+                .get(V4PlusStyleFields::Alignment)
                 .parse()
                 .map_err(ASSElementReaderError::Custom)?,
-            outline: parse_float(outline, "outline")?,
-            shadow: parse_float(shadow, "shadow")?,
-            alignment: alignment.parse().map_err(ASSElementReaderError::Custom)?,
-            margin_l: parse_int(marginl, "margin_l")?,
-            margin_r: parse_int(marginr, "margin_r")?,
-            margin_v: parse_int(marginv, "margin_v")?,
+            margin_l: fields.parsed(V4PlusStyleFields::MarginL, parse_int, "margin_l")?,
+            margin_r: fields.parsed(V4PlusStyleFields::MarginR, parse_int, "margin_r")?,
+            margin_v: fields.parsed(V4PlusStyleFields::MarginV, parse_int, "margin_v")?,
         };
 
         match self.styles.insert(name.to_string(), style) {
@@ -154,30 +269,54 @@ impl ASSReader {
     }
 
     fn read_event(&mut self, line: &str) -> Result<(), ASSElementReaderError> {
-        let fields: Vec<_> = line.trim().splitn(8, ',').map(|str| str.trim()).collect();
-        let [marked, layer, start, end, style, name, effect, text] = &fields[..] else {
-            return Err(ASSElementReaderError::Custom(format!("invalid line composed of fields: {fields:#?}")))
+        let line = if line.starts_with("Format:") {
+            let line = line.split_once(':').unwrap().1.trim();
+            self.events_format = line
+                .split(',')
+                .flat_map(|str| str.trim().parse::<EventFields>())
+                .collect();
+            return match self.events_format.last() {
+                Some(EventFields::Text) => Ok(()),
+                _ => Err(ASSElementReaderError::Custom(format!(
+                    "invalid format line: {:?}",
+                    self.events_format
+                ))),
+            };
+        } else {
+            line.split_once(':').unwrap().1.trim()
         };
+        let fields = line.splitn(self.events_format.len(), ',').collect::<Vec<_>>();
+        if fields.len() != self.events_format.len() {
+            return Err(ASSElementReaderError::Custom(format!(
+                "style format specified {} fields, got {} in the style description",
+                self.events_format.len(),
+                fields.len()
+            )));
+        }
+        let fields: UnwrapHashMap<EventFields> = UnwrapHashMap(HashMap::from_iter(
+            self.events_format
+                .iter()
+                .copied()
+                .zip(fields.into_iter().map(|s| s.trim())),
+        ));
         self.events.push(ASSEvent {
-            marked: parse_boolean(marked, "marked")?,
-            layer: parse_int(layer, "layer")?,
-            start: parse_date(start, "start")?,
-            end: parse_date(end, "end")?,
-            style: style.to_string(),
-            name: name.to_string(),
-            effect: effect.to_string(),
-            text: text.to_string(),
+            marked: fields.parsed_or(EventFields::Marked, parse_boolean, "marked", false),
+            layer: fields.parsed_or(EventFields::Layer, parse_int, "layer", 0),
+            start: fields.parsed(EventFields::Start, parse_date, "start")?,
+            end: fields.parsed(EventFields::End, parse_date, "end")?,
+            style: fields.get_or(EventFields::Style, "Default").to_string(),
+            name: fields.get_or(EventFields::Name, "").to_string(),
+            effect: fields.get_or(EventFields::Effect, "").to_string(),
+            text: fields.get(EventFields::Text).to_string(),
         });
         Ok(())
     }
 }
 
 impl ASSElementReader for ASSReader {
-    fn try_read(
-        mut self,
-        file: impl std::io::BufRead,
-    ) -> Result<crate::ASSContainerPtr, ASSElementReaderError> {
+    fn try_read(mut self, file: impl std::io::BufRead) -> Result<ASSContainer, ASSElementReaderError> {
         // Parse the file
+        let mut skip_that_section = false;
         for line in file.lines() {
             let line = line.map_err(ASSElementReaderError::FailedToReadLine)?;
             let line = line.trim();
@@ -185,11 +324,16 @@ impl ASSElementReader for ASSReader {
             if line.is_empty() || line.starts_with(';') {
                 continue;
             } else if line.starts_with('[') && line.ends_with(']') {
-                self.section = Some(
-                    line.parse::<ASSFileSection>()
-                        .map_err(ASSElementReaderError::Custom)?,
-                );
-            } else {
+                skip_that_section = false;
+                self.section = match line.parse::<ASSFileSection>() {
+                    Ok(section) => Some(section),
+                    Err(err) => {
+                        log::error!(target: "ass", "{err}");
+                        skip_that_section = true;
+                        continue;
+                    }
+                };
+            } else if !skip_that_section {
                 match self.section {
                     Some(ASSFileSection::ScriptInfo) => self.read_script_info(line)?,
                     Some(ASSFileSection::V4Styles) => self.read_v4_style(line)?,
@@ -206,8 +350,7 @@ impl ASSElementReader for ASSReader {
         // Verify integrity + order events
         self.events.sort_by(|a, b| Ord::cmp(&a.start, &b.end));
         if !self.styles.contains_key(ASSStyle::default_name()) {
-            self.styles
-                .insert(ASSStyle::default_name_string(), ASSStyle::default());
+            self.styles.insert(ASSStyle::default_name_string(), ASSStyle::default());
         }
         for ASSEvent { style, .. } in &mut self.events {
             if !self.styles.contains_key(style) {
@@ -215,11 +358,13 @@ impl ASSElementReader for ASSReader {
             }
         }
 
-        Ok(ASSContainer::from_parts(
-            ASSLines::default().into_ptr(),
-            self.script_info,
-            self.styles,
-        )
-        .into_ptr())
+        // Convert events into lines...
+        let mut lines = Vec::with_capacity(self.events.len());
+        for event in self.events {
+            let ASSEvent { start, end, .. } = event;
+            lines.push(ASSLine { start, fini: end, ..Default::default() });
+        }
+
+        Ok(ASSContainer::from_parts(lines, self.script_info, self.styles))
     }
 }
diff --git a/src/Rust/vvs_ass/src/reader/json.rs b/src/Rust/vvs_ass/src/reader/json.rs
index e972d937..b6df2266 100644
--- a/src/Rust/vvs_ass/src/reader/json.rs
+++ b/src/Rust/vvs_ass/src/reader/json.rs
@@ -1,4 +1,4 @@
-use crate::{reader::ASSElementReader, ASSElementReaderError};
+use crate::{reader::ASSElementReader, ASSContainer, ASSElementReaderError};
 
 /// Documentation available here: http://www.tcax.org/docs/ass-specs.html or in the `utils/manual`
 /// folder.
@@ -6,10 +6,7 @@ use crate::{reader::ASSElementReader, ASSElementReaderError};
 pub struct JSONReader {}
 
 impl ASSElementReader for JSONReader {
-    fn try_read(
-        self,
-        _file: impl std::io::BufRead,
-    ) -> Result<crate::ASSContainerPtr, ASSElementReaderError> {
+    fn try_read(self, _file: impl std::io::BufRead) -> Result<ASSContainer, ASSElementReaderError> {
         todo!()
     }
 }
diff --git a/src/Rust/vvs_ass/src/reader/mod.rs b/src/Rust/vvs_ass/src/reader/mod.rs
index fb96abcb..70f904a9 100644
--- a/src/Rust/vvs_ass/src/reader/mod.rs
+++ b/src/Rust/vvs_ass/src/reader/mod.rs
@@ -1,17 +1,25 @@
 //! Read the content of an ASS file / a Vivy subtitle file and creates an
 //! [vvs_ass::elements::lines::ASSLinesPtr] structure accordingly.
 
-use crate::ASSContainerPtr;
+use crate::ASSContainer;
 use std::{
     fs::File,
     io::{BufReader, Error as IoError},
     path::{Path, PathBuf},
 };
 use thiserror::Error;
+use vvs_procmacro::EnumVariantFromStr;
 
 mod ass;
 mod json;
 
+#[derive(Debug, EnumVariantFromStr)]
+pub enum ContainerFileType {
+    ASS,
+    VVSB,
+    Json,
+}
+
 #[derive(Debug, Error)]
 pub enum ASSElementReaderError {
     #[error("file has no extension: {0}")]
@@ -31,28 +39,32 @@ pub enum ASSElementReaderError {
 }
 
 trait ASSElementReader {
-    fn try_read(
-        self,
-        file: impl std::io::BufRead,
-    ) -> Result<ASSContainerPtr, ASSElementReaderError>;
+    fn try_read(self, file: impl std::io::BufRead) -> Result<ASSContainer, ASSElementReaderError>;
 }
 
-pub fn ass_lines_from_file(
-    file: impl AsRef<Path>,
-) -> Result<ASSContainerPtr, ASSElementReaderError> {
+pub fn ass_container_from_file(file: impl AsRef<Path>) -> Result<ASSContainer, ASSElementReaderError> {
     let file = file.as_ref();
     let Some(extension) = file.extension() else {
         return Err(ASSElementReaderError::NoExtension(file.to_path_buf()));
     };
     let content = BufReader::new(
-        File::open(file)
-            .map_err(|err| ASSElementReaderError::FailedToOpenFile(file.to_path_buf(), err))?,
+        File::open(file).map_err(|err| ASSElementReaderError::FailedToOpenFile(file.to_path_buf(), err))?,
     );
-    match &extension.to_string_lossy()[..] {
+    match &extension.to_string_lossy().to_lowercase()[..] {
         "ass" => ass::ASSReader::default().try_read(content),
         "vvsb" | "json" => json::JSONReader::default().try_read(content),
-        extension => Err(ASSElementReaderError::UnknownExtension(
-            extension.to_string(),
-        )),
+        extension => Err(ASSElementReaderError::UnknownExtension(extension.to_string())),
+    }
+}
+
+pub fn ass_container_from_str(
+    extension: ContainerFileType,
+    str: impl AsRef<str>,
+) -> Result<ASSContainer, ASSElementReaderError> {
+    use ContainerFileType::*;
+    let content = BufReader::new(str.as_ref().as_bytes());
+    match extension {
+        ASS => ass::ASSReader::default().try_read(content),
+        VVSB | Json => json::JSONReader::default().try_read(content),
     }
 }
diff --git a/src/Rust/vvs_ass/src/styles.rs b/src/Rust/vvs_ass/src/styles.rs
index 9b2a8ea7..81252927 100644
--- a/src/Rust/vvs_ass/src/styles.rs
+++ b/src/Rust/vvs_ass/src/styles.rs
@@ -105,9 +105,7 @@ impl std::str::FromStr for ASSBorderStyle {
         match s.trim() {
             "1" => Ok(ASSBorderStyle::OutlineAndDropShadow),
             "3" => Ok(ASSBorderStyle::OpaqueBox),
-            s => Err(format!(
-                "invalid value '{s}' for ASSBorderStyle, must be 1 or 3"
-            )),
+            s => Err(format!("invalid value '{s}' for ASSBorderStyle, must be 1 or 3")),
         }
     }
 }
diff --git a/src/Rust/vvs_ass/src/tests.rs b/src/Rust/vvs_ass/src/tests.rs
index e26c65d2..02e67175 100644
--- a/src/Rust/vvs_ass/src/tests.rs
+++ b/src/Rust/vvs_ass/src/tests.rs
@@ -74,4 +74,13 @@ mod color {
             eq! { b_target, b, "invalid convertion on blue for #{rgb}: {b_target} != {b}"}
         }
     }
+
+    #[test]
+    fn test_parse_empty_ass() {
+        use crate::reader::ass_container_from_str;
+        let content = include_str!("../utils/empty.ass");
+        if let Err(err) = ass_container_from_str(reader::ContainerFileType::ASS, content) {
+            panic!("{err}")
+        }
+    }
 }
diff --git a/src/Rust/vvs_ass/src/types.rs b/src/Rust/vvs_ass/src/types.rs
index dedd4a63..a3db2667 100644
--- a/src/Rust/vvs_ass/src/types.rs
+++ b/src/Rust/vvs_ass/src/types.rs
@@ -1,13 +1,10 @@
-use std::str::FromStr;
-use thiserror::Error;
-use vvs_procmacro::{EnumVariantCount, EnumVariantIter};
+use serde::{Deserialize, Serialize};
+use vvs_procmacro::{EnumVariantCount, EnumVariantFromStr, EnumVariantIter};
 
 /// Represents the types of the ASS types that can be manipulated. By combining them we can create
 /// a tree of the ASS elements. We have:
 /// ```ignore
 /// - Lines
-///   - pos: AssPosition
-///   - aux: HashMap<String, AssAuxValue>
 ///   - content:
 ///     [0] := Line
 ///     ... ... ...
@@ -17,8 +14,6 @@ use vvs_procmacro::{EnumVariantCount, EnumVariantIter};
 ///   - aux: HashMap<String, AssAuxValue>
 ///   - content: Syllabes
 /// - Syllabes
-///   - pos: AssPosition
-///   - aux: HashMap<String, AssAuxValue>
 ///   - content:
 ///     [0] := Syllabe
 ///     ... ... ...
@@ -31,7 +26,21 @@ use vvs_procmacro::{EnumVariantCount, EnumVariantIter};
 ///
 /// We also derive the Ord/PartialOrd crates. The types are ordered from the Lines to the Syllabe.
 /// Thus if a type is greater than another one, the former must contains the latter.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumVariantCount, EnumVariantIter)]
+#[derive(
+    Debug,
+    Clone,
+    Copy,
+    PartialEq,
+    Eq,
+    PartialOrd,
+    Ord,
+    Hash,
+    Serialize,
+    Deserialize,
+    EnumVariantCount,
+    EnumVariantIter,
+    EnumVariantFromStr,
+)]
 pub enum ASSType {
     Lines = 0,
     Line = 1,
@@ -60,13 +69,21 @@ impl ASSType {
         }
     }
 
-    /// Get the base type of the ASS type
+    /// Returns the base type.
     pub fn base_type(&self) -> Self {
         match self {
             ASSType::Lines | ASSType::Line => ASSType::Line,
             ASSType::Syllabes | ASSType::Syllabe => ASSType::Syllabe,
         }
     }
+
+    /// Returns the vec type.
+    pub fn vec_type(&self) -> Self {
+        match self {
+            ASSType::Lines | ASSType::Line => ASSType::Lines,
+            ASSType::Syllabes | ASSType::Syllabe => ASSType::Syllabes,
+        }
+    }
 }
 
 impl AsRef<str> for ASSType {
@@ -80,23 +97,3 @@ impl std::fmt::Display for ASSType {
         f.write_str(self.as_str())
     }
 }
-
-#[derive(Debug, Error)]
-pub enum ASSTypeFromStrError {
-    #[error("unknown ass structure type '{0}', expected: lines/line/syllabe/syllabes")]
-    Unknown(String),
-}
-
-impl FromStr for ASSType {
-    type Err = ASSTypeFromStrError;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "line" => Ok(Self::Line),
-            "lines" => Ok(Self::Lines),
-            "syllabe" => Ok(Self::Syllabe),
-            "syllabes" => Ok(Self::Syllabes),
-            _ => Err(ASSTypeFromStrError::Unknown(s.to_string())),
-        }
-    }
-}
diff --git a/src/Rust/vvs_ass/src/values.rs b/src/Rust/vvs_ass/src/values.rs
index 1dce8df8..32a16819 100644
--- a/src/Rust/vvs_ass/src/values.rs
+++ b/src/Rust/vvs_ass/src/values.rs
@@ -1,7 +1,8 @@
+use serde::{Deserialize, Serialize};
 use std::{collections::HashMap, convert::TryFrom};
 
 /// The values that can be added to an ASS element.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, PartialEq, Serialize, Deserialize)]
 pub enum ASSAuxValue {
     Integer(i64),
     Floating(f64),
@@ -13,14 +14,22 @@ pub enum ASSAuxValue {
 #[derive(Debug, Default, Clone, PartialEq)]
 pub struct ASSAuxTable(HashMap<String, ASSAuxValue>);
 
-/// A pointer type for the [ASSAuxTable] type, for easy wrapping
-pub type ASSAuxTablePtr = crate::Ptr<ASSAuxTable>;
+impl std::fmt::Debug for ASSAuxValue {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Integer(arg0) => write!(f, "{arg0}"),
+            Self::Floating(arg0) => write!(f, "{arg0}"),
+            Self::String(arg0) => write!(f, "{arg0:?}"),
+            Self::Boolean(arg0) => write!(f, "{arg0}"),
+        }
+    }
+}
 
 impl ASSAuxValue {
     pub fn type_str(&self) -> &'static str {
         match self {
-            ASSAuxValue::Integer(_) => "integer",
             ASSAuxValue::Floating(_) => "floating",
+            ASSAuxValue::Integer(_) => "integer",
             ASSAuxValue::Boolean(_) => "boolean",
             ASSAuxValue::String(_) => "string",
         }
@@ -56,6 +65,78 @@ impl ASSAuxValue {
     }
 }
 
+impl From<String> for ASSAuxValue {
+    fn from(value: String) -> Self {
+        Self::String(value)
+    }
+}
+
+impl From<&str> for ASSAuxValue {
+    fn from(value: &str) -> Self {
+        Self::String(value.to_string())
+    }
+}
+
+impl From<bool> for ASSAuxValue {
+    fn from(value: bool) -> Self {
+        Self::Boolean(value)
+    }
+}
+
+impl From<f32> for ASSAuxValue {
+    fn from(value: f32) -> Self {
+        Self::Floating(value as f64)
+    }
+}
+
+impl From<f64> for ASSAuxValue {
+    fn from(value: f64) -> Self {
+        Self::Floating(value)
+    }
+}
+
+impl From<i64> for ASSAuxValue {
+    fn from(value: i64) -> Self {
+        Self::Integer(value)
+    }
+}
+
+impl From<i32> for ASSAuxValue {
+    fn from(value: i32) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
+impl From<u32> for ASSAuxValue {
+    fn from(value: u32) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
+impl From<i16> for ASSAuxValue {
+    fn from(value: i16) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
+impl From<u16> for ASSAuxValue {
+    fn from(value: u16) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
+impl From<i8> for ASSAuxValue {
+    fn from(value: i8) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
+impl From<u8> for ASSAuxValue {
+    fn from(value: u8) -> Self {
+        Self::Integer(value as i64)
+    }
+}
+
 impl std::fmt::Display for ASSAuxValue {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
@@ -72,10 +153,6 @@ impl ASSAuxTable {
         Default::default()
     }
 
-    pub fn into_ptr(self) -> ASSAuxTablePtr {
-        crate::ptr! { self }
-    }
-
     pub fn set(&mut self, name: impl AsRef<str>, value: ASSAuxValue) {
         let name = name.as_ref();
         let new = value.type_str();
@@ -107,6 +184,15 @@ impl ASSAuxTable {
     }
 }
 
+impl IntoIterator for ASSAuxTable {
+    type Item = <HashMap<String, ASSAuxValue> as IntoIterator>::Item;
+    type IntoIter = <HashMap<String, ASSAuxValue> as IntoIterator>::IntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
+
 impl FromIterator<(String, ASSAuxValue)> for ASSAuxTable {
     fn from_iter<T: IntoIterator<Item = (String, ASSAuxValue)>>(iter: T) -> Self {
         Self(HashMap::from_iter(iter))
diff --git a/src/Rust/vvs_ass/utils/empty.ass b/src/Rust/vvs_ass/utils/empty.ass
new file mode 100644
index 00000000..d57acc0b
--- /dev/null
+++ b/src/Rust/vvs_ass/utils/empty.ass
@@ -0,0 +1,19 @@
+[Script Info]
+; Script generated by Aegisub 3.3.3
+; http://www.aegisub.org/
+Title: Default Aegisub file
+ScriptType: v4.00+
+WrapStyle: 0
+ScaledBorderAndShadow: yes
+YCbCr Matrix: None
+
+[Aegisub Project Garbage]
+
+[V4+ Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
+
+[Events]
+Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,
+
diff --git a/src/Rust/vvs_cli/Cargo.toml b/src/Rust/vvs_cli/Cargo.toml
index e7656212..cb68d735 100644
--- a/src/Rust/vvs_cli/Cargo.toml
+++ b/src/Rust/vvs_cli/Cargo.toml
@@ -16,9 +16,10 @@ vvs_repl = { path = "../vvs_repl" }
 vvs_font = { path = "../vvs_font" }
 vvs_utils = { path = "../vvs_utils" }
 
-lazy_static.workspace = true
 thiserror.workspace = true
 anyhow.workspace = true
+serde.workspace = true
+toml.workspace = true
 log.workspace = true
 scc.workspace = true
 
diff --git a/src/Rust/vvs_cli/src/args.rs b/src/Rust/vvs_cli/src/args.rs
index 26fd7ad1..aefb7496 100644
--- a/src/Rust/vvs_cli/src/args.rs
+++ b/src/Rust/vvs_cli/src/args.rs
@@ -9,6 +9,7 @@ use std::path::PathBuf;
          , about
          , name = "vvcc"
          , group = clap::ArgGroup::new("action").args(["manpage", "shell", "font-file", "script.vvs"])
+         , group = clap::ArgGroup::new("ass")   .args(["subtitle.ass"]).conflicts_with_all(["manpage", "font-file"])
          , group = clap::ArgGroup::new("repl")  .args(["interactive"]) .conflicts_with_all(["shell", "manpage", "font-file"])
          , group = clap::ArgGroup::new("opts")  .args(["options.toml"]).conflicts_with_all(["shell", "manpage", "font-file"])
          , group = clap::ArgGroup::new("infos") .args(["info"])        .conflicts_with_all(["shell", "manpage", "font-file"])
@@ -25,7 +26,19 @@ pub struct Args {
     )]
     pub script: Option<PathBuf>,
 
-    /// The option file that will be used when running the script
+    /// The input ASS file to execute the script on.
+    ///
+    /// The supported subtitle extensions are the ASS files (V4+) or the Json files exported by the
+    /// Vivy application.
+    #[arg ( short        = 'f'
+          , long         = "subtitle"
+          , action       = clap::ArgAction::Set
+          , id           = "subtitle.ass"
+          , value_parser = FileTypeValueParser::new("ass"),
+    )]
+    pub ass_file: Option<PathBuf>,
+
+    /// The option file that will be used when running the script.
     #[arg( short        = 't'
          , long         = "option"
          , action       = clap::ArgAction::Set
@@ -34,6 +47,18 @@ pub struct Args {
     )]
     pub options: Option<PathBuf>,
 
+    /// The includes' folder list, in order.
+    ///
+    /// Will search for modules to import in those folders. Note that there is no need to add the
+    /// folder of the loaded script as it will automatically be added in the top position of the
+    /// include list.
+    #[arg( short  = 'I'
+         , long   = "include-path"
+         , action = clap::ArgAction::Append
+         , id     = "include"
+    )]
+    pub include_folders: Vec<PathBuf>,
+
     /// Launch vvcc REPL after loading the passed script if any.
     ///
     /// In REPL mode you must not touch to fields that begins by underscores, never. Those fields
diff --git a/src/Rust/vvs_cli/src/config.rs b/src/Rust/vvs_cli/src/config.rs
new file mode 100644
index 00000000..38b4d304
--- /dev/null
+++ b/src/Rust/vvs_cli/src/config.rs
@@ -0,0 +1,115 @@
+use crate::args::Args;
+use clap_complete::Shell;
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+use vvs_utils::*;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ConfigKaraMaker {
+    #[serde(rename = "name")]
+    kara_maker: String,
+
+    email: Option<String>,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+pub struct ConfigFile {
+    #[serde(rename = "includes")]
+    include_folders: Vec<PathBuf>,
+
+    karamaker: ConfigKaraMaker,
+}
+
+#[derive(Debug, Default)]
+pub struct Config {
+    // From config::ConfigFile
+    pub kara_maker: String,
+
+    // From args::Args
+    pub script: Option<PathBuf>,
+    pub ass_file: Option<PathBuf>,
+    pub options: Option<PathBuf>,
+    pub interactive: bool,
+    pub info: bool,
+    pub manpage: bool,
+    pub shell: Option<Shell>,
+    pub font_info: Option<Option<PathBuf>>,
+    pub verbose: u8,
+
+    // Merge of the two
+    pub include_folders: Vec<PathBuf>,
+}
+
+macro_rules! merge {
+    ($($what: ident $src: expr => $dest: expr, { $( $field: ident ),+ });+ $(;)?) => {{
+        $(merge! { @@internal $what $( $field ),+ : $src => $dest });+;
+    }};
+
+    (@@internal override $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field  = $src.$field; )+}};
+    (@@internal flg_or   $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field |= $src.$field; )+}};
+    (@@internal flg_and  $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field &= $src.$field; )+}};
+
+    (@@internal append   $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field.extend($src.$field); )+}};
+
+    (@@internal max      $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field = max_partial!($src.$field, $dest.$field); )+}};
+    (@@internal min      $( $field: ident ),+ : $src: expr => $dest: expr) => {{$( $dest.$field = min_partial!($src.$field, $dest.$field); )+}};
+
+    (@@internal set_if_not $( $field: ident),+ : $src: expr => $dest: expr) => {{$(
+        if $dest.$field.is_none() {
+            $dest.$field = $src.$field;
+        }
+    )+}};
+}
+
+impl Extend<ConfigFile> for ConfigFile {
+    fn extend<T: IntoIterator<Item = ConfigFile>>(&mut self, iter: T) {
+        iter.into_iter().for_each(|other| {
+            let (km, self_km) = (other.karamaker, &mut self.karamaker);
+            merge! {
+                append   other => self,    { include_folders };
+                override km    => self_km, { kara_maker };
+            }
+        });
+    }
+}
+
+impl Extend<ConfigFile> for Config {
+    fn extend<T: IntoIterator<Item = ConfigFile>>(&mut self, iter: T) {
+        iter.into_iter().for_each(|cfg| {
+            let km = cfg.karamaker;
+            merge! {
+                append   cfg => self, { include_folders };
+                override km  => self, { kara_maker };
+            }
+        });
+    }
+}
+
+impl Extend<Args> for Config {
+    fn extend<T: IntoIterator<Item = Args>>(&mut self, iter: T) {
+        iter.into_iter().for_each(|args| {
+            merge! {
+                append     args => self, { include_folders };
+                set_if_not args => self, { script, ass_file, options, shell, font_info };
+                max        args => self, { verbose };
+                override   args => self, { info, manpage, interactive };
+            }
+        });
+    }
+}
+
+impl ConfigFile {
+    pub fn serialize(&self) -> Result<String, Box<dyn std::error::Error>> {
+        Ok(toml::to_string_pretty(self).map_err(Box::new)?)
+    }
+
+    pub fn deserialize(input: String) -> Result<Self, Box<dyn std::error::Error>> {
+        Ok(toml::from_str(&input).map_err(Box::new)?)
+    }
+}
+
+impl Default for ConfigKaraMaker {
+    fn default() -> Self {
+        Self { kara_maker: "Viieux".to_string(), email: Default::default() }
+    }
+}
diff --git a/src/Rust/vvs_cli/src/lib.rs b/src/Rust/vvs_cli/src/lib.rs
index 08c54c71..2c31f840 100644
--- a/src/Rust/vvs_cli/src/lib.rs
+++ b/src/Rust/vvs_cli/src/lib.rs
@@ -1,4 +1,7 @@
+#![forbid(unsafe_code)]
+
 pub mod args;
+pub mod config;
 pub mod logger;
 
 mod parser;
diff --git a/src/Rust/vvs_cli/src/logger.rs b/src/Rust/vvs_cli/src/logger.rs
index 270a8526..e0e0a408 100644
--- a/src/Rust/vvs_cli/src/logger.rs
+++ b/src/Rust/vvs_cli/src/logger.rs
@@ -1,7 +1,6 @@
-use lazy_static::lazy_static;
 use log::{Level, Metadata, Record, SetLoggerError};
 use scc::HashSet;
-use std::sync::atomic::AtomicU8;
+use std::sync::{atomic::AtomicU8, OnceLock};
 use thiserror::Error;
 
 #[derive(Debug, Error)]
@@ -19,8 +18,10 @@ struct SimpleLoggerRef {
     inner: SimpleLogger,
 }
 
-lazy_static! {
-    static ref LOGGER: SimpleLoggerRef = Default::default();
+static LOGGER: OnceLock<SimpleLoggerRef> = OnceLock::new();
+
+fn logger() -> &'static SimpleLogger {
+    &LOGGER.get().unwrap().inner
 }
 
 impl std::fmt::Display for LoggerInitError {
@@ -30,17 +31,19 @@ impl std::fmt::Display for LoggerInitError {
 }
 
 impl SimpleLogger {
-    fn write_str<S: AsRef<str>>(lvl: char, target: &str, content: S) {
+    fn write_str<S: AsRef<str>>(level: char, prefix: String, target: &str, content: S) {
         let prefix = if target.is_empty() {
-            format!("{lvl} ")
+            format!("{level} [{prefix}]")
+        } else if prefix.is_empty() {
+            format!("{level} [{target}] ")
         } else {
-            format!("{lvl} [{target}] ")
+            format!("{level} [{prefix} {target}] ")
         };
-        content
-            .as_ref()
-            .lines()
-            .filter(|content| !content.trim().is_empty())
-            .for_each(|content| eprintln!("{prefix}{content}"));
+        let mut content = content.as_ref().lines().filter(|content| !content.trim().is_empty());
+        if let Some(content) = content.next() {
+            eprintln!("{prefix}{content}");
+        }
+        content.for_each(|content| eprintln!("{level} {content}"));
     }
 
     fn level(&self) -> Level {
@@ -68,10 +71,16 @@ impl log::Log for SimpleLogger {
                 Level::Debug => '.',
                 Level::Trace => ' ',
             };
+            let prefix = match (record.file(), record.line()) {
+                (None, None) => "".to_string(),
+                (None, Some(line)) => format!("...+{line}"),
+                (Some(file), None) => format!("{file}+..."),
+                (Some(file), Some(line)) => format!("{file}+{line}"),
+            };
             if let Some(s) = record.args().as_str() {
-                SimpleLogger::write_str(level, record.target(), s)
+                SimpleLogger::write_str(level, prefix, record.target(), s)
             } else {
-                SimpleLogger::write_str(level, record.target(), record.args().to_string());
+                SimpleLogger::write_str(level, prefix, record.target(), record.args().to_string());
             }
         }
     }
@@ -80,22 +89,20 @@ impl log::Log for SimpleLogger {
 }
 
 pub fn level(lvl: u8) {
-    LOGGER
-        .inner
-        .level
-        .store(lvl, std::sync::atomic::Ordering::SeqCst);
-    log::set_max_level(LOGGER.inner.level().to_level_filter());
+    logger().level.store(lvl, std::sync::atomic::Ordering::SeqCst);
+    log::set_max_level(LOGGER.get().unwrap().inner.level().to_level_filter());
 }
 
 pub fn ignore_target(target: &'static str) {
-    let _ = LOGGER.inner.ignore_targets.insert(target);
+    let _ = logger().ignore_targets.insert(target);
 }
 
 pub fn init(level: Option<Level>) -> Result<(), LoggerInitError> {
-    log::set_logger(&LOGGER.inner)
+    LOGGER.set(Default::default()).expect("failed to set default logger...");
+    log::set_logger(logger())
         .map(|()| {
             log::set_max_level(match level {
-                None => LOGGER.inner.level().to_level_filter(),
+                None => logger().level().to_level_filter(),
                 Some(level) => {
                     match level {
                         Level::Trace => self::level(3),
diff --git a/src/Rust/vvs_cli/src/main.rs b/src/Rust/vvs_cli/src/main.rs
index 72abf27b..3cf3f1a9 100644
--- a/src/Rust/vvs_cli/src/main.rs
+++ b/src/Rust/vvs_cli/src/main.rs
@@ -1,11 +1,16 @@
 //! The VivyScript cli
+#![forbid(unsafe_code)]
 
 use anyhow::{Context, Result};
-use vvs_cli::{args, logger};
+use vvs_cli::{
+    args,
+    config::{Config, ConfigFile},
+    logger,
+};
+use vvs_utils::xdg::*;
 
 fn print_face_info(name: &str, font: &[u8]) -> Result<()> {
-    let font =
-        vvs_font::Font::try_from(font).with_context(|| format!("failed to parse font {name}"))?;
+    let font = vvs_font::Font::try_from(font).with_context(|| format!("failed to parse font {name}"))?;
 
     let font_types = font
         .font_types()
@@ -29,16 +34,28 @@ fn print_face_info(name: &str, font: &[u8]) -> Result<()> {
 fn main() -> Result<()> {
     logger::init(None).map_err(Box::new)?;
     logger::ignore_target("rustyline");
-    let args::Args {
-        interactive,
-        verbose,
+
+    let mut config = Config::default();
+    config.extend([
+        XDGConfig::<ConfigFile, XDGConfigMergedSilent>::new("vvcc", ConfigFile::deserialize)
+            .file("vvcc.toml")
+            .read_or_default(ConfigFile::serialize),
+    ]);
+    config.extend([<args::Args as clap::Parser>::parse()]);
+    let Config {
         script,
+        ass_file,
         options,
-        shell,
-        manpage,
+        interactive,
         info,
+        manpage,
+        shell,
         font_info,
-    } = <args::Args as clap::Parser>::parse();
+        verbose,
+        include_folders,
+        ..
+    } = config;
+
     logger::level(verbose);
 
     if manpage {
@@ -57,8 +74,8 @@ fn main() -> Result<()> {
     } else if let Some(font_info) = font_info {
         match font_info {
             Some(path) => {
-                let font = std::fs::read(&path)
-                    .with_context(|| format!("failed to read font: {}", path.to_string_lossy()))?;
+                let font =
+                    std::fs::read(&path).with_context(|| format!("failed to read font: {}", path.to_string_lossy()))?;
                 print_face_info(&path.to_string_lossy(), &font)?;
             }
             None => {
@@ -77,13 +94,18 @@ fn main() -> Result<()> {
             .with_context(|| "failed to print help message for vvcc");
     }
 
-    let lua = vvs_lua::setup(match options {
-        Some(ref options) => {
-            log::debug!(target: "vvcc", "load option file: {}", options.to_string_lossy());
-            Some(vvs_lua::TomlOptions::new_from_toml(options)?)
-        }
-        None => None,
-    })
+    let lua = vvs_lua::setup(
+        ass_file,
+        match options {
+            Some(ref options) => {
+                log::debug!(target: "vvcc", "load option file: {}", options.to_string_lossy());
+                Some(vvs_lua::TomlOptions::new_from_toml(options)?)
+            }
+            None => None,
+        },
+        include_folders,
+        interactive,
+    )
     .with_context(|| match options {
         Some(options) => format!(
             "failed to setup base runtime and/or load options: {}",
diff --git a/src/Rust/vvs_cli/src/parser.rs b/src/Rust/vvs_cli/src/parser.rs
index 73c9ba00..21f29bfd 100644
--- a/src/Rust/vvs_cli/src/parser.rs
+++ b/src/Rust/vvs_cli/src/parser.rs
@@ -26,10 +26,7 @@ impl TypedValueParser for FileTypeValueParser {
     ) -> Result<Self::Value, clap::Error> {
         let mut err = clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd);
         if let Some(arg) = arg {
-            err.insert(
-                ContextKind::InvalidArg,
-                ContextValue::String(arg.to_string()),
-            );
+            err.insert(ContextKind::InvalidArg, ContextValue::String(arg.to_string()));
         }
         match value.to_ascii_lowercase().to_str() {
             Some(value) => match value.trim().split('.').last() {
@@ -38,10 +35,7 @@ impl TypedValueParser for FileTypeValueParser {
                     .ok_or_else(|| {
                         err.insert(
                             ContextKind::InvalidValue,
-                            ContextValue::String(format!(
-                                "invalid extension {file_ext}, expected {}",
-                                self.extension
-                            )),
+                            ContextValue::String(format!("invalid extension {file_ext}, expected {}", self.extension)),
                         );
                         err
                     }),
diff --git a/src/Rust/vvs_font/Cargo.toml b/src/Rust/vvs_font/Cargo.toml
index f1cd7653..838aa4cf 100644
--- a/src/Rust/vvs_font/Cargo.toml
+++ b/src/Rust/vvs_font/Cargo.toml
@@ -7,7 +7,6 @@ license.workspace = true
 description = "The font crate for VVS"
 
 [dependencies]
-lazy_static.workspace = true
 thiserror.workspace = true
 log.workspace = true
 
diff --git a/src/Rust/vvs_font/build.rs b/src/Rust/vvs_font/build.rs
index 0ae1bc9d..aaf4757c 100644
--- a/src/Rust/vvs_font/build.rs
+++ b/src/Rust/vvs_font/build.rs
@@ -48,8 +48,7 @@ pub const fn embeded_fonts() -> &'static [(&'static str, &'static [u8])] {{
     );
 
     // Write
-    fs::write(out_dir.join("generated_font_utils.rs"), src_content)
-        .expect("failed to write generated source file");
+    fs::write(out_dir.join("generated_font_utils.rs"), src_content).expect("failed to write generated source file");
 
     // Rerun
     rerun_directory(&font_dir);
diff --git a/src/Rust/vvs_font/fonts/OFL.txt b/src/Rust/vvs_font/fonts/NotoSans-LICENCE-OFL.txt
similarity index 100%
rename from src/Rust/vvs_font/fonts/OFL.txt
rename to src/Rust/vvs_font/fonts/NotoSans-LICENCE-OFL.txt
diff --git a/src/Rust/vvs_font/src/font.rs b/src/Rust/vvs_font/src/font.rs
index d51bf15b..5fc71f22 100644
--- a/src/Rust/vvs_font/src/font.rs
+++ b/src/Rust/vvs_font/src/font.rs
@@ -27,8 +27,7 @@ impl<'a> TryFrom<&'a [u8]> for Font<'a> {
     fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
         Ok(Self {
             face: ttf_parser::Face::parse(data, 0).map_err(FontCreationError::TTFParserError)?,
-            font: ab_glyph::FontRef::try_from_slice(data)
-                .map_err(FontCreationError::ABGlyphError)?,
+            font: ab_glyph::FontRef::try_from_slice(data).map_err(FontCreationError::ABGlyphError)?,
         })
     }
 }
@@ -47,15 +46,10 @@ impl<'a> Font<'a> {
     /// Get the family names.
     pub fn family_names(&self) -> Vec<String> {
         let names = self.face.names();
-        let filter = [
-            ttf_parser::name_id::POST_SCRIPT_NAME,
-            ttf_parser::name_id::FULL_NAME,
-        ];
+        let filter = [ttf_parser::name_id::POST_SCRIPT_NAME, ttf_parser::name_id::FULL_NAME];
         names
             .into_iter()
-            .flat_map(|name| {
-                (filter.contains(&name.name_id) && name.is_unicode()).then(|| name.to_string())
-            })
+            .flat_map(|name| (filter.contains(&name.name_id) && name.is_unicode()).then(|| name.to_string()))
             .flatten()
             .collect()
     }
@@ -102,14 +96,8 @@ impl<'a> Font<'a> {
             .map(|outlined| {
                 let ab_glyph::Rect { min, max } = outlined.px_bounds();
                 Rect::new(
-                    Point {
-                        x: min.x.trunc() as i64,
-                        y: min.y.trunc() as i64,
-                    },
-                    Point {
-                        x: max.x.trunc() as i64,
-                        y: max.y.trunc() as i64,
-                    },
+                    Point { x: min.x.trunc() as i64, y: min.y.trunc() as i64 },
+                    Point { x: max.x.trunc() as i64, y: max.y.trunc() as i64 },
                 )
             })
             .ok_or(FontError::FailedToOutline(pt, glyph))
diff --git a/src/Rust/vvs_font/src/lib.rs b/src/Rust/vvs_font/src/lib.rs
index ba8e8634..b631d37b 100644
--- a/src/Rust/vvs_font/src/lib.rs
+++ b/src/Rust/vvs_font/src/lib.rs
@@ -1,3 +1,5 @@
+#![forbid(unsafe_code)]
+
 mod error;
 mod font;
 mod rect;
diff --git a/src/Rust/vvs_font/src/rect.rs b/src/Rust/vvs_font/src/rect.rs
index 28c7be4f..86454e17 100644
--- a/src/Rust/vvs_font/src/rect.rs
+++ b/src/Rust/vvs_font/src/rect.rs
@@ -45,17 +45,11 @@ impl Rect {
 impl Point {
     /// Returns the min components of the two [Point].
     pub fn min(p1: Point, p2: Point) -> Point {
-        Point {
-            x: min(p1.x, p2.x),
-            y: min(p1.y, p2.y),
-        }
+        Point { x: min(p1.x, p2.x), y: min(p1.y, p2.y) }
     }
 
     /// Returns the max components of the two [Point].
     pub fn max(p1: Point, p2: Point) -> Point {
-        Point {
-            x: max(p1.x, p2.x),
-            y: max(p1.y, p2.y),
-        }
+        Point { x: max(p1.x, p2.x), y: max(p1.y, p2.y) }
     }
 }
diff --git a/src/Rust/vvs_lua/Cargo.toml b/src/Rust/vvs_lua/Cargo.toml
index 9443ce92..481d1a0c 100644
--- a/src/Rust/vvs_lua/Cargo.toml
+++ b/src/Rust/vvs_lua/Cargo.toml
@@ -10,9 +10,11 @@ description = "The lua wrapper for VVS"
 vvs_utils = { path = "../vvs_utils" }
 vvs_ass = { path = "../vvs_ass" }
 
+unicode-segmentation.workspace = true
 thiserror.workspace = true
+serde.workspace = true
 paste.workspace = true
 toml.workspace = true
+mlua.workspace = true
 log.workspace = true
-
-mlua = { version = "^0.8", features = ["luajit52", "vendored", "macros"] }
+scc.workspace = true
diff --git a/src/Rust/vvs_lua/build.rs b/src/Rust/vvs_lua/build.rs
index 50064850..ee12de74 100644
--- a/src/Rust/vvs_lua/build.rs
+++ b/src/Rust/vvs_lua/build.rs
@@ -17,10 +17,9 @@ fn main() {
     let out_dir = Path::new(&env::var_os("OUT_DIR").expect("no OUT_DIR env variable..."))
         .canonicalize()
         .expect("failed to canonicalize OUT_DIR");
-    let base_path =
-        PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("can't find the manifest path"))
-            .canonicalize()
-            .expect("failed to canonicalize CARGO_MANIFEST_DIR");
+    let base_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("can't find the manifest path"))
+        .canonicalize()
+        .expect("failed to canonicalize CARGO_MANIFEST_DIR");
 
     // Utils for the stdlib
     let stdlib_path = base_path.join("src/libs");
diff --git a/src/Rust/vvs_lua/src/data/actions.rs b/src/Rust/vvs_lua/src/data/actions.rs
index d11fa665..f2ed025b 100644
--- a/src/Rust/vvs_lua/src/data/actions.rs
+++ b/src/Rust/vvs_lua/src/data/actions.rs
@@ -52,9 +52,9 @@ impl<'lua> FromLua<'lua> for RegisterDataValue {
                 });
                 if value.ty().ne(&ty) {
                     return Err(LuaError::RuntimeError(format!(
-                            "tried to register a value of type '{}' when declaring the option of type '{ty}'",
-                            value.ty()
-                        )));
+                        "tried to register a value of type '{}' when declaring the option of type '{ty}'",
+                        value.ty()
+                    )));
                 }
                 Ok(Self { doc, ty, value })
             }
@@ -62,9 +62,7 @@ impl<'lua> FromLua<'lua> for RegisterDataValue {
             _ => Err(LuaError::FromLuaConversionError {
                 from: lua_value.type_name(),
                 to: "RegisterDataValue",
-                message: Some(
-                    "expected a string or a table with the 'data' instruction".to_string(),
-                ),
+                message: Some("expected a string or a table with the 'data' instruction".to_string()),
             }),
         }
     }
diff --git a/src/Rust/vvs_lua/src/data/register.rs b/src/Rust/vvs_lua/src/data/register.rs
index af8fbaf9..e51a022b 100644
--- a/src/Rust/vvs_lua/src/data/register.rs
+++ b/src/Rust/vvs_lua/src/data/register.rs
@@ -1,9 +1,8 @@
-use crate::{data::actions::RegisterDataValue, lua_wrapper::LuaAssAuxTablePtr};
+use crate::data::actions::RegisterDataValue;
 use mlua::prelude::*;
 use std::{
-    cell::RefCell,
     collections::{HashMap, HashSet},
-    rc::Rc,
+    sync::{Arc, RwLock},
 };
 use thiserror::Error;
 use vvs_ass::{ASSAuxTable, ASSType, ASSTYPE_LENGTH, ASSTYPE_VALUES};
@@ -12,7 +11,7 @@ use vvs_ass::{ASSAuxTable, ASSType, ASSTYPE_LENGTH, ASSTYPE_VALUES};
 struct VivyDataValueHashMap {
     ass_type: ASSType,
     table: HashMap<String, RegisterDataValue>,
-    cached_table: LuaAssAuxTablePtr,
+    cached_table: ASSAuxTable,
 }
 
 #[derive(Debug, Error)]
@@ -34,7 +33,7 @@ pub(crate) struct VivyDataRegister {
     will_register: HashSet<String>,
 }
 
-pub(crate) type VivyDataRegisterPtr = Rc<RefCell<VivyDataRegister>>;
+pub(crate) type VivyDataRegisterPtr = Arc<RwLock<VivyDataRegister>>;
 
 fn decode_name(name: &str) -> LuaResult<(&str, ASSType)> {
     let Some((ass_type, name)) = name.split_once(':') else {
@@ -55,7 +54,7 @@ impl VivyDataValueHashMap {
         Self {
             ass_type: ty,
             table: Default::default(),
-            cached_table: LuaAssAuxTablePtr::from(ASSAuxTable::default().into_ptr()),
+            cached_table: ASSAuxTable::default(),
         }
     }
 }
@@ -82,14 +81,11 @@ impl VivyDataRegister {
     pub fn compute_cached_tables(&mut self) {
         for ty in ASSTYPE_VALUES {
             let register = self.get_register_mut(*ty);
-            let table = LuaAssAuxTablePtr::from(
-                ASSAuxTable::from_iter(
-                    register
-                        .table
-                        .iter()
-                        .map(|(name, data)| (name.clone(), data.value.clone().into_inner())),
-                )
-                .into_ptr(),
+            let table = ASSAuxTable::from_iter(
+                register
+                    .table
+                    .iter()
+                    .map(|(name, data)| (name.clone(), data.value.clone().into_inner())),
             );
             register.cached_table = table;
         }
@@ -111,17 +107,15 @@ impl VivyDataRegister {
     /// Get the default aux table for an ass element type. Returns the cached table. After
     /// registering all the wanted values, you may use the
     /// [VivyDataRegister::compute_cached_tables] before calling this function.
-    pub fn get_table(&self, ass_type: ASSType) -> LuaAssAuxTablePtr {
+    pub fn get_table(&self, ass_type: ASSType) -> ASSAuxTable {
         self.get_register(ass_type).cached_table.clone()
     }
 
     /// Iter over the registered data names and types.
     pub fn iter_registered(&self) -> impl Iterator<Item = (ASSType, &String)> {
-        self.registered.iter().flat_map(
-            |VivyDataValueHashMap {
-                 ass_type, table, ..
-             }| { std::iter::repeat(*ass_type).zip(table.keys()) },
-        )
+        self.registered
+            .iter()
+            .flat_map(|VivyDataValueHashMap { ass_type, table, .. }| std::iter::repeat(*ass_type).zip(table.keys()))
     }
 
     /// Get the register data for a specific [ASSType]. If the ass type is not correct the function
@@ -155,11 +149,7 @@ impl VivyDataRegister {
 
     /// Register the data, returns an error if the data was already registered. If the data was not
     /// flagged as 'to register', also raise an error.
-    fn register_data(
-        &mut self,
-        name: impl AsRef<str>,
-        value: RegisterDataValue,
-    ) -> Result<(), VivyDataRegisterError> {
+    fn register_data(&mut self, name: impl AsRef<str>, value: RegisterDataValue) -> Result<(), VivyDataRegisterError> {
         let (name, ass_type) = decode_name(name.as_ref()).map_err(VivyDataRegisterError::Lua)?;
         let table = &mut self.get_register_mut(ass_type).table;
         match table.get(name) {
@@ -184,11 +174,7 @@ impl VivyDataRegister {
             for (ty, data_name, format_name) in added {
                 println!(
                     "   - data       {format_name:padding$} = {}",
-                    self.get_table(ty)
-                        .as_inner()
-                        .borrow()
-                        .get(data_name)
-                        .expect("vivy internal error"),
+                    self.get_table(ty).get(data_name).expect("vivy internal error"),
                 );
             }
         }
@@ -201,43 +187,34 @@ impl VivyDataRegister {
 
 impl LuaUserData for VivyDataRegister {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_meta_function("_name", |lua, ()| lua.create_string("VivyDataRegister"));
+
         methods.add_method_mut("will_register", |_, this, name: String| {
-            this.will_register_data(name.clone())
-                .map_err(|err| match err {
-                    VivyDataRegisterError::Lua(lua) => lua,
-                    err => LuaError::RuntimeError(format!(
-                        "failed to mark as 'to register' data '{name}': {err}"
-                    )),
-                })
+            this.will_register_data(name.clone()).map_err(|err| match err {
+                VivyDataRegisterError::Lua(lua) => lua,
+                err => LuaError::RuntimeError(format!("failed to mark as 'to register' data '{name}': {err}")),
+            })
         });
 
-        methods.add_method_mut(
-            "register",
-            |_, this, (name, value): (String, RegisterDataValue)| {
-                this.register_data(&name, value).map_err(|err| match err {
-                    VivyDataRegisterError::Lua(lua) => lua,
-                    err => {
-                        LuaError::RuntimeError(format!("failed to register data '{name}': {err}"))
-                    }
-                })
-            },
-        )
+        methods.add_method_mut("register", |_, this, (name, value): (String, RegisterDataValue)| {
+            this.register_data(&name, value).map_err(|err| match err {
+                VivyDataRegisterError::Lua(lua) => lua,
+                err => LuaError::RuntimeError(format!("failed to register data '{name}': {err}")),
+            })
+        })
     }
 }
 
 #[test]
 fn test_data_register_creation() {
     let reg = VivyDataRegister::new();
-    let reg: &VivyDataRegister = &reg.borrow();
+    let reg: &VivyDataRegister = &reg.try_read().unwrap();
     assert_eq!(*reg, Default::default());
 
     macro_rules! test_item {
         ($item: ident) => {
             assert_eq!(
-                reg.registered
-                    .get(ASSType::$item as usize)
-                    .unwrap()
-                    .ass_type,
+                reg.registered.get(ASSType::$item as usize).unwrap().ass_type,
                 ASSType::$item
             );
         };
diff --git a/src/Rust/vvs_lua/src/dsl.rs b/src/Rust/vvs_lua/src/dsl.rs
index 67448bd7..cba49266 100644
--- a/src/Rust/vvs_lua/src/dsl.rs
+++ b/src/Rust/vvs_lua/src/dsl.rs
@@ -30,11 +30,7 @@ pub(crate) use value;
 
 use mlua::{chunk, prelude::*};
 
-pub(crate) fn into_readonly_table<'lua>(
-    lua: &'lua Lua,
-    name: LuaString<'lua>,
-    table: LuaTable<'lua>,
-) -> LuaResult<LuaTable<'lua>> {
+pub(crate) fn into_readonly_table<'lua>(lua: &'lua Lua, table: LuaTable<'lua>) -> LuaResult<LuaTable<'lua>> {
     let (proxy, mt) = (lua.create_table()?, lua.create_table()?);
     mt.raw_set("__index", table)?;
     mt.raw_set(
@@ -44,27 +40,19 @@ pub(crate) fn into_readonly_table<'lua>(
                 error("attempt to update a read-only table", 2)
             end
         })
-        .set_name(format!("{}::ro-function", name.to_str()?))?
         .eval::<LuaFunction>()?,
     )?;
     proxy.set_metatable(Some(mt));
     Ok(proxy)
 }
 
-pub(crate) fn into_string_vec<'lua>(
-    lua: &'lua Lua,
-    value: LuaValue<'lua>,
-) -> LuaResult<Vec<String>> {
+pub(crate) fn into_string_vec<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<Vec<String>> {
     match value {
-        ret @ LuaValue::Boolean(_) | ret @ LuaValue::Integer(_) | ret @ LuaValue::Number(_) => {
-            Ok(vec![lua
-                .coerce_string(ret)?
-                .ok_or(LuaError::RuntimeError(
-                    "coertion to string failed".to_string(),
-                ))?
-                .to_string_lossy()
-                .to_string()])
-        }
+        ret @ LuaValue::Boolean(_) | ret @ LuaValue::Integer(_) | ret @ LuaValue::Number(_) => Ok(vec![lua
+            .coerce_string(ret)?
+            .ok_or(LuaError::RuntimeError("coertion to string failed".to_string()))?
+            .to_string_lossy()
+            .to_string()]),
         LuaValue::String(str) => Ok(vec![str.to_string_lossy().to_string()]),
         LuaValue::Table(table) => Ok(table
             .sequence_values::<LuaString>()
diff --git a/src/Rust/vvs_lua/src/func/actions.rs b/src/Rust/vvs_lua/src/func/actions.rs
index 81c1aeab..892a0458 100644
--- a/src/Rust/vvs_lua/src/func/actions.rs
+++ b/src/Rust/vvs_lua/src/func/actions.rs
@@ -1,6 +1,6 @@
 use crate::dsl;
 use mlua::prelude::*;
-use std::rc::Rc;
+use std::sync::Arc;
 
 /// Structure used to parse a function declaration, like in:
 /// - `func "toto" { function(...) ... end }`
@@ -12,12 +12,11 @@ pub(super) struct FuncDeclaration<'lua> {
 
 /// Structure used to represent a function in memory.
 #[derive(Debug)]
-#[allow(dead_code)]
-pub(super) struct VivyFunc {
-    pub module: Option<String>,
+pub(crate) struct VivyFunc {
+    pub module: String,
     pub name: String,
     pub doc: Option<String>,
-    pub function: Rc<LuaRegistryKey>,
+    pub function: Arc<LuaRegistryKey>,
 }
 
 impl<'lua> FromLua<'lua> for FuncDeclaration<'lua> {
@@ -43,10 +42,7 @@ impl<'lua> FromLua<'lua> for FuncDeclaration<'lua> {
             .filter_map(|pair| Some(pair.ok()?.1))
             .collect();
         match &callback[..] {
-            [function] => Ok(Self {
-                doc,
-                function: function.clone(),
-            }),
+            [function] => Ok(Self { doc, function: function.clone() }),
             [] => Err(LuaError::RuntimeError(
                 "expected a function to implement the vivy function export, got nothing".to_string(),
             )),
@@ -60,12 +56,9 @@ impl<'lua> FromLua<'lua> for FuncDeclaration<'lua> {
 
 impl LuaUserData for VivyFunc {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
-        methods.add_meta_method(
-            LuaMetaMethod::Call,
-            |lua, this, arguments: LuaMultiValue| {
-                lua.registry_value::<LuaFunction>(&this.function)?
-                    .call::<_, LuaMultiValue>(arguments)
-            },
-        )
+        methods.add_meta_method(LuaMetaMethod::Call, |lua, this, arguments: LuaMultiValue| {
+            lua.registry_value::<LuaFunction>(&this.function)?
+                .call::<_, LuaMultiValue>(arguments)
+        })
     }
 }
diff --git a/src/Rust/vvs_lua/src/func/mod.rs b/src/Rust/vvs_lua/src/func/mod.rs
index ca2913e6..6a2d643a 100644
--- a/src/Rust/vvs_lua/src/func/mod.rs
+++ b/src/Rust/vvs_lua/src/func/mod.rs
@@ -1,4 +1,5 @@
 mod actions;
 mod register;
 
+pub(crate) use actions::VivyFunc;
 pub(crate) use register::{VivyFuncRegister, VivyFuncRegisterPtr};
diff --git a/src/Rust/vvs_lua/src/func/register.rs b/src/Rust/vvs_lua/src/func/register.rs
index d373c00a..80c63e7e 100644
--- a/src/Rust/vvs_lua/src/func/register.rs
+++ b/src/Rust/vvs_lua/src/func/register.rs
@@ -1,9 +1,8 @@
 use crate::func::actions::{FuncDeclaration, VivyFunc};
 use mlua::{chunk, prelude::*};
 use std::{
-    cell::RefCell,
     collections::{HashMap, HashSet},
-    rc::Rc,
+    sync::{Arc, RwLock},
 };
 
 /// The structure used to register funcs and do the resolution at runtime.
@@ -18,13 +17,13 @@ pub(crate) struct VivyFuncRegister {
 
     /// All the func that are declared in the current module. Must be cleared after a module is
     /// loaded. If its length is not equal to [VivyFuncRegister::func_names], then we have a problem.
-    local_funcs: HashMap<String, Rc<VivyFunc>>,
+    local_funcs: HashMap<String, Arc<RwLock<VivyFunc>>>,
 
     /// All func declared in all modules.
-    all_declared_funcs: Vec<Rc<VivyFunc>>,
+    all_declared_funcs: Vec<Arc<RwLock<VivyFunc>>>,
 }
 
-pub(crate) type VivyFuncRegisterPtr = Rc<RefCell<VivyFuncRegister>>;
+pub(crate) type VivyFuncRegisterPtr = Arc<RwLock<VivyFuncRegister>>;
 
 impl VivyFuncRegister {
     pub fn new() -> VivyFuncRegisterPtr {
@@ -33,12 +32,7 @@ impl VivyFuncRegister {
 
     /// Export the local funcs into the named module. We also reset the content of the register for
     /// the next module. This should be called once, when we finished to parse the module.
-    pub(crate) fn export(
-        &mut self,
-        lua: &Lua,
-        name: &LuaString,
-        table: &LuaTable,
-    ) -> LuaResult<()> {
+    pub(crate) fn export(&mut self, lua: &Lua, name: &LuaString, table: &LuaTable) -> LuaResult<()> {
         if self.func_names.len() != self.local_funcs.len() {
             return Err(LuaError::RuntimeError(format!(
                 "invalid func declarations in module '{}', declaration count doesn't match the registration count",
@@ -50,21 +44,26 @@ impl VivyFuncRegister {
             let func = self.local_funcs.remove(&name).expect("undefined func");
             table.raw_set(
                 lua.create_string(&name)?,
-                lua.registry_value::<LuaFunction>(&func.function)?,
+                lua.registry_value::<LuaFunction>(&func.try_read().unwrap().function)?,
             )?;
         }
 
         Ok(())
     }
+
+    /// Iterate over all declared functions.
+    pub(crate) fn iter_declared(&self) -> std::slice::Iter<Arc<RwLock<VivyFunc>>> {
+        self.all_declared_funcs.iter()
+    }
 }
 
 impl LuaUserData for VivyFuncRegister {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_meta_function("_name", |lua, ()| lua.create_string("VivyFuncRegister"));
+
         methods.add_method_mut("will_register", |_, this, name: String| -> LuaResult<()> {
             if this.func_names.contains(&name) {
-                Err(LuaError::RuntimeError(format!(
-                    "redefinition of func '{name}'"
-                )))
+                Err(LuaError::RuntimeError(format!("redefinition of func '{name}'")))
             } else {
                 this.func_names.insert(name);
                 Ok(())
@@ -81,27 +80,24 @@ impl LuaUserData for VivyFuncRegister {
                         "try to register func '{name}' but it was not declared"
                     )));
                 } else if this.local_funcs.contains_key(name) {
-                    return Err(LuaError::RuntimeError(format!(
-                        "try to register func '{name}' again"
-                    )));
+                    return Err(LuaError::RuntimeError(format!("try to register func '{name}' again")));
                 }
 
-                let declaration = Rc::new(VivyFunc {
-                    module: match lua
-                        .load(chunk!(return vivy: ___current_module()))
-                        .set_name("query current module name")?
-                        .eval::<LuaValue>()
-                    {
-                        Ok(LuaValue::String(str)) => Some(str.to_string_lossy().to_string()),
-                        _ => None,
+                let declaration = Arc::new(RwLock::new(VivyFunc {
+                    module: match lua.load(chunk!(return vivy: ___current_module())).eval::<LuaValue>() {
+                        Ok(LuaValue::String(str)) => str.to_string_lossy().to_string(),
+                        _ => {
+                            return Err(LuaError::RuntimeError(format!(
+                                "failed to register function '{name}', failed to query the current module name"
+                            )))
+                        }
                     },
                     name: name.to_string(),
                     doc: table.doc,
                     function: lua.create_registry_value(table.function)?.into(),
-                });
+                }));
 
-                this.local_funcs
-                    .insert(name.to_string(), declaration.clone());
+                this.local_funcs.insert(name.to_string(), declaration.clone());
                 this.all_declared_funcs.push(declaration);
 
                 Ok(())
diff --git a/src/Rust/vvs_lua/src/functions.rs b/src/Rust/vvs_lua/src/functions.rs
index e94a939f..cbf5bba4 100644
--- a/src/Rust/vvs_lua/src/functions.rs
+++ b/src/Rust/vvs_lua/src/functions.rs
@@ -1,223 +1,43 @@
-use crate::{
-    data::VivyDataRegister, func::VivyFuncRegister, jobs::VivyJobRegister, libs,
-    options::VivyOptionRegister, TomlOptions,
-};
+use crate::{libs::vivy::*, TomlOptions};
 use mlua::{chunk, prelude::*};
-use std::path::Path;
-
-pub fn setup(options: Option<TomlOptions>) -> LuaResult<Lua> {
-    let (version, options, data, jobs, func) = (
-        env!("CARGO_PKG_VERSION"),
-        VivyOptionRegister::new(options.unwrap_or_default()),
-        VivyDataRegister::new(),
-        VivyJobRegister::new(),
-        VivyFuncRegister::new(),
-    );
-    let lua = unsafe {
-        // We use unsafe because we need to load the debug package.
-        // This also allows to load C things and dylib things, but because we nil the requires
-        // function it should be fine.
-        Lua::unsafe_new_with(
-            LuaStdLib::MATH
-                | LuaStdLib::TABLE
-                | LuaStdLib::STRING
-                | LuaStdLib::PACKAGE
-                | LuaStdLib::DEBUG,
-            LuaOptions::new().catch_rust_panics(true),
-        )
-    };
-    let (requires, warning, info) = (
-        lua.create_function(libs::import)?,
-        lua.create_function(|_, msg: String| {
-            log::warn!(target: "vvs", "{msg}");
-            Ok(())
-        })?,
-        lua.create_function(|_, msg: String| {
-            log::info!(target: "vvs", "{msg}");
-            Ok(())
-        })?,
-    );
-
-    lua.load(chunk! {
-        local _debug     = debug
-        require          = nil
-        module           = nil
-        _VERSION         = nil
-        local options    = $options
-        local data       = $data
-        local jobs       = $jobs
-        local func       = $func
-        local ReadOnly   = {
-            package   = nil,
-            coroutine = nil,
-            debug     = nil,
-            _VERSION  = "Vivy " .. $version,
-            warning   = $warning,
-            info      = $info,
-        }
-
-        // Import Vivy
-        $requires "vivy"
-        vivy:___set_options(options)
-        vivy:___set_data(data)
-        vivy:___set_jobs(jobs)
-        vivy:___set_func(func)
-        ReadOnly.vivy = vivy
-        vivy          = nil
-
-        package.loaded["package"]   = nil
-        package.loaded["debug"]     = nil
-        package.loaded["coroutine"] = nil
-        package.loaded["_G"]        = nil
-        for name, _ in pairs(package.loaded) do
-            ReadOnly.vivy:___mark_loaded(name)
-        end
-        package = nil
-
-        function ReadOnly._job_input_type (func)
-            local nparams = _debug.getinfo(func).nparams
-            if nparams ~= 1 then
-                error("a job should only have one argument, got " .. nparams, 2)
-            end
-            return _debug.getlocal(func, 1)
-        end
-
-        // DSL keywords
-
-        function ReadOnly.import (name)
-            if type(name) ~= "string" then
-                error("you must import a module by its name, got " .. type(name) .. " and not a string", 2)
-            end
-            $requires(name)
-        end
-
-        function ReadOnly.main (table)
-            if type(table) ~= "table" then
-                error("you must specify the main workflow with a table", 2)
-            end
-            vivy:___main(table)
-        end
-
-        function ReadOnly.func (name)
-            if type(name) ~= "string" then
-                error("you must specify the exported function name with a string", 2)
-            end
-            return function (table)
-                if type(table) ~= "table" then
-                    error("you must specify the function description with a table", 2)
-                end
-                error("not implemented")
-            end
-        end
-
-        function ReadOnly.job (name)
-            if type(name) ~= "string" then
-                error("you must specify the job name with a string", 2)
-            end
-            jobs:will_register(name)
-            return function (table)
-                if type(table) ~= "table" then
-                    error("you must specify the job description with a table", 2)
-                end
-                jobs:register(name, table)
-            end
-        end
-
-        function ReadOnly.data (name)
-            if type(name) ~= "string" then
-                error("you must specify the datum name with a string", 2)
-            end
-            data:will_register(name)
-            return function (table)
-                if type(table) ~= "table" then
-                    error("you must specify the data description with a table", 2)
-                end
-                data:register(name, table)
-            end
-        end
-
-        function ReadOnly.set (name)
-            if type(name) ~= "string" then
-                error("you must specify the option name with a string", 2)
-            end
-            options:will_set(name)
-            return function (table)
-                if type(table) ~= "table" then
-                    error("you must specify the option specification description with a table", 2)
-                end
-                options:set(name, table)
-            end
-        end
-
-        function ReadOnly.option (name)
-            if type(name) ~= "string" then
-                error("you must specify the option name with a string", 2)
-            end
-            options:will_register(name)
-            return function (table)
-                if type(table) ~= "table" then
-                    error("you must specify the option description with a table", 2)
-                end
-                options:register(name, table)
-            end
-        end
-
-        // Protect the metatable
-
-        setmetatable(_G, {
-            __metatable = "don't touch to the global table",
-            __index     = ReadOnly,
-            __newindex  = function(t, n, v)
-                if rawget(ReadOnly, n) ~= nil then
-                    error("keyword or read only variable '" .. n .. "'", 2)
-                end
-                if n:upper() ~= n then
-                    error("global variables must be in uppercase, try '" .. n:upper() .. "'", 2)
-                end
-                rawset(t, n, v)
-            end,
-        })
+use std::path::{Path, PathBuf};
+use vvs_utils::either;
+
+pub fn setup(
+    ass_file: Option<impl AsRef<Path>>,
+    options: Option<TomlOptions>,
+    include_path: Vec<PathBuf>,
+    interactive_mode: bool,
+) -> LuaResult<Lua> {
+    Vivy::setup_runtime(RuntimeOptions {
+        include_path,
+        ass_file: ass_file.map(|path| ASSFileOrInstance::File(path.as_ref().to_path_buf())),
+        creation_data: options.map(RuntimeCreationData::TomlFile),
+        mode: either!(interactive_mode => RuntimeMode::Interactive; RuntimeMode::Full),
     })
-    .set_name("vivy prelude")?
-    .exec()?;
-
-    Ok(lua)
 }
 
 pub fn load_user_script(lua: &Lua, script: Option<impl AsRef<Path>>) -> LuaResult<()> {
     if let Some(script) = script {
         let script = script.as_ref();
-        match script.parent() {
-            Some(parent_folder) => {
-                let (path1, path2) = (parent_folder.join("?.vvl"), parent_folder.join("?.lua"));
-                let (path1, path2) = (path1.to_string_lossy(), path2.to_string_lossy());
-                let name = format!("append paths {path1} and {path2}");
-                lua.load(chunk! {
-                    vivy:___append_to_path($path1)
-                    vivy:___append_to_path($path2)
-                })
-                .set_name(name)?
-                .exec()?;
-            }
-            None => {
-                log::warn!(target: "lua", "the script has no parent folder: {}", script.to_string_lossy())
-            }
+        if let Some(parent_folder) = script.parent() {
+            let (path1, path2) = (parent_folder.join("?.vvl"), parent_folder.join("?.lua"));
+            let (path1, path2) = (path1.to_string_lossy(), path2.to_string_lossy());
+            lua.load(chunk! {
+                vivy:___prepend_to_path($path1)
+                vivy:___prepend_to_path($path2)
+            })
+            .exec()?;
         }
         let script_path = script.to_string_lossy();
+        let script_path_lua = lua.create_string(script_path.as_ref())?;
         log::debug!(target: "lua", "load the script: {script_path}");
+        lua.load(chunk! { vivy:___set_user_script($script_path_lua) }).exec()?;
         lua.load(script).set_name(script_path)?.exec()?;
     }
     Ok(())
 }
 
 pub fn print_info(lua: &Lua) -> LuaResult<()> {
-    lua.load(mlua::chunk! { vivy:___print_info() })
-        .set_name("vivy:___print_info")?
-        .exec()
-}
-
-pub fn get_loaded(lua: &Lua) -> LuaResult<Vec<String>> {
-    lua.load(mlua::chunk! { vivy:___get_loaded() })
-        .set_name("vivy:___get_loaded")?
-        .eval()
+    lua.load(mlua::chunk! { vivy:___print_info() }).exec()
 }
diff --git a/src/Rust/vvs_lua/src/jobs/actions.rs b/src/Rust/vvs_lua/src/jobs/actions.rs
index 2338638f..2d8c911a 100644
--- a/src/Rust/vvs_lua/src/jobs/actions.rs
+++ b/src/Rust/vvs_lua/src/jobs/actions.rs
@@ -1,6 +1,6 @@
 use crate::{dsl, lua_wrapper::LuaAssType};
-use mlua::{chunk, prelude::*};
-use std::{rc::Rc, sync::Arc};
+use mlua::prelude::*;
+use std::sync::Arc;
 use vvs_ass::ASSType;
 use vvs_utils::*;
 
@@ -10,35 +10,36 @@ use vvs_utils::*;
 pub(super) struct JobDeclaration<'lua> {
     pub doc: Option<String>,
     pub function: LuaFunction<'lua>,
-    pub input_type: Option<ASSType>,
-    pub return_type: Option<ASSType>,
+    pub input_type: ASSType,
+    pub return_type: ASSType,
 }
 
 /// Structure used to represent a job in memory.
 #[derive(Debug)]
-pub(super) struct VivyJob {
-    pub module: Option<String>,
+pub(crate) struct VivyJob {
+    pub module: String,
     pub name: String,
     pub doc: Option<String>,
-    pub input_type: Option<ASSType>,
-    pub return_type: Option<ASSType>,
-    pub function: Rc<LuaRegistryKey>,
+    pub input_type: ASSType,
+    pub return_type: ASSType,
+    pub function: Arc<LuaRegistryKey>,
 }
 
 /// Structure used to represent a job that will be called on some values. Used because of execution
 /// order of lua, when we build the main tree the variables are not present, we need to declare
 /// them latter after the tree was validated.
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq, Hash)]
 pub(crate) struct VivyCalledJob {
+    pub module: String,
     pub name: String,
-    pub function: Rc<LuaRegistryKey>,
+    pub function: Arc<LuaRegistryKey>,
     pub arguments: Vec<String>,
-    pub input_type: Option<ASSType>,
-    pub return_type: Option<ASSType>,
+    pub input_type: ASSType,
+    pub return_type: ASSType,
 }
 
 impl<'lua> FromLua<'lua> for JobDeclaration<'lua> {
-    fn from_lua(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
+    fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
         let LuaValue::Table(table) = lua_value else {
             return Err(LuaError::RuntimeError(format!(
                 "expected a table to convert into a job registration, got a: {}",
@@ -55,6 +56,13 @@ impl<'lua> FromLua<'lua> for JobDeclaration<'lua> {
             ))),
         });
 
+        let getter = dsl::table_get!(table, "_getter" -> {
+            dsl::value!(Function fun) => fun,
+            _ => return Err(LuaError::RuntimeError(
+                "internal error: the '_getter' field should be present when registering a job...".to_string()
+            )),
+        });
+
         let callback: Vec<_> = table
             .pairs::<LuaString, LuaValue>()
             .filter_map(|res| match res {
@@ -67,24 +75,22 @@ impl<'lua> FromLua<'lua> for JobDeclaration<'lua> {
             .collect();
         match &callback[..] {
             [(return_type, function)] => Ok({
-                let input_type = {
-                    let func = function.clone();
-                    lua.load(chunk! { return _job_input_type($func) })
-                        .eval::<LuaString>()?
-                        .to_str()?
-                        .parse::<LuaAssType>()
-                        .map_err(LuaError::RuntimeError)?
-                        .into_inner()
-                };
+                let input_type = getter
+                    .call::<_, LuaString>(function.clone())?
+                    .to_str()?
+                    .parse::<LuaAssType>()
+                    .map_err(LuaError::RuntimeError)?
+                    .into_inner();
                 Self {
                     doc,
                     function: function.clone(),
-                    return_type: Some(*return_type.as_inner()),
-                    input_type: Some(input_type),
+                    return_type: *return_type.as_inner(),
+                    input_type,
                 }
             }),
             [] => Err(LuaError::RuntimeError(
-                "expected a function to implement the job, got nothing".to_string(),
+                "expected a function to implement the job, got nothing, did you specify a valid ASS element name?"
+                    .to_string(),
             )),
             _ => Err(LuaError::RuntimeError(format!(
                 "expected a single function to implement the job, got {} functions",
@@ -101,9 +107,7 @@ impl LuaUserData for VivyJob {
                 LuaNil => vec![],
                 LuaValue::String(name) => vec![name.to_string_lossy().to_string()],
                 LuaValue::Table(names) => {
-                    let (names, errs): (Vec<_>, Vec<_>) = names
-                        .sequence_values::<LuaString>()
-                        .partition(Result::is_ok);
+                    let (names, errs): (Vec<_>, Vec<_>) = names.sequence_values::<LuaString>().partition(Result::is_ok);
                     if let Some(Err(err)) = errs.into_iter().next() {
                         return Err(err);
                     }
@@ -119,19 +123,10 @@ impl LuaUserData for VivyJob {
                     )))
                 }
             };
-            let signature = {
-                let name = match this.module {
-                    Some(ref module) => format!("{module}.{}", this.name),
-                    None => this.name.clone(),
-                };
-                let signature = match (this.input_type, this.return_type) {
-                    (None, None) => "() -> ()".to_string(),
-                    (None, Some(rty)) => format!("() -> {rty}"),
-                    (Some(ity), None) => format!("{ity} -> ()"),
-                    (Some(ity), Some(rty)) => format!("{ity} -> {rty}"),
-                };
-                format!("{name}: {signature}")
-            };
+            let signature = format!(
+                "{}.{}: {} -> {}",
+                this.module, this.name, this.input_type, this.return_type
+            );
             log::info!(
                 target: "lua",
                 "will execute {} job `{signature}` with argsuments: {arguments:?}",
@@ -139,6 +134,7 @@ impl LuaUserData for VivyJob {
             );
             Ok(VivyCalledJob {
                 arguments,
+                module: this.module.clone(),
                 name: this.name.clone(),
                 function: this.function.clone(),
                 input_type: this.input_type,
@@ -148,36 +144,4 @@ impl LuaUserData for VivyJob {
     }
 }
 
-impl LuaUserData for VivyCalledJob {
-    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
-        methods.add_meta_method(LuaMetaMethod::Call, |lua, this, ()| {
-            if !lua
-                .load(chunk! { return vivy:___is_in_job_evaluation_mode() })
-                .set_name("is in job evaluation mode?")?
-                .eval::<bool>()?
-            {
-                todo!()
-            }
-            lua.load(chunk! { vivy:___switch_to_execution_mode() })
-                .set_name("switch to execution mode")?
-                .exec()?;
-
-            let (name, arguments) = (this.name.clone(), this.arguments.clone());
-            let function: LuaFunction = lua.registry_value(&this.function)?;
-            let (Some(input), Some(returns)) = (this.input_type, this.return_type) else {
-                    return Err(LuaError::RuntimeError(format!(
-                        "failed to get the input and return types for the job '{name}'"
-                    )));
-                };
-            let (input, returns) = (LuaAssType::from(input), LuaAssType::from(returns));
-            lua.load(chunk! {
-                vivy:___execute_job($name, $function, $arguments, $input, $returns)
-            })
-            .eval::<LuaMultiValue>()
-            .map_err(|cause| LuaError::CallbackError {
-                traceback: "error in job execution".to_string(),
-                cause: Arc::new(cause),
-            })
-        });
-    }
-}
+impl LuaUserData for VivyCalledJob {}
diff --git a/src/Rust/vvs_lua/src/jobs/mod.rs b/src/Rust/vvs_lua/src/jobs/mod.rs
index f844ef32..772cbf0e 100644
--- a/src/Rust/vvs_lua/src/jobs/mod.rs
+++ b/src/Rust/vvs_lua/src/jobs/mod.rs
@@ -1,5 +1,5 @@
 mod actions;
 mod register;
 
-pub(crate) use actions::VivyCalledJob;
+pub(crate) use actions::{VivyCalledJob, VivyJob};
 pub(crate) use register::{VivyJobRegister, VivyJobRegisterPtr};
diff --git a/src/Rust/vvs_lua/src/jobs/register.rs b/src/Rust/vvs_lua/src/jobs/register.rs
index 3cbe96dd..4579039d 100644
--- a/src/Rust/vvs_lua/src/jobs/register.rs
+++ b/src/Rust/vvs_lua/src/jobs/register.rs
@@ -1,9 +1,8 @@
 use crate::jobs::actions::{JobDeclaration, VivyJob};
 use mlua::{chunk, prelude::*};
 use std::{
-    cell::RefCell,
     collections::{HashMap, HashSet},
-    rc::Rc,
+    sync::{Arc, RwLock},
 };
 
 /// The structure used to register jobs and do the resolution at runtime.
@@ -18,13 +17,13 @@ pub(crate) struct VivyJobRegister {
 
     /// All the job that are declared in the current module. Must be cleared after a module is
     /// loaded. If its length is not equal to [VivyJobRegister::job_names], then we have a problem.
-    local_jobs: HashMap<String, Rc<RefCell<VivyJob>>>,
+    local_jobs: HashMap<String, Arc<RwLock<VivyJob>>>,
 
     /// All job declared in all modules.
-    all_declared_jobs: Vec<Rc<RefCell<VivyJob>>>,
+    all_declared_jobs: Vec<Arc<RwLock<VivyJob>>>,
 }
 
-pub(crate) type VivyJobRegisterPtr = Rc<RefCell<VivyJobRegister>>;
+pub(crate) type VivyJobRegisterPtr = Arc<RwLock<VivyJobRegister>>;
 
 impl VivyJobRegister {
     pub fn new() -> VivyJobRegisterPtr {
@@ -33,12 +32,7 @@ impl VivyJobRegister {
 
     /// Export the local jobs into the named module. We also reset the content of the register for
     /// the next module. This should be called once, when we finished to parse the module.
-    pub(crate) fn export(
-        &mut self,
-        lua: &Lua,
-        name: &LuaString,
-        table: &LuaTable,
-    ) -> LuaResult<()> {
+    pub(crate) fn export(&mut self, lua: &Lua, name: &LuaString, table: &LuaTable) -> LuaResult<()> {
         if self.job_names.len() != self.local_jobs.len() {
             return Err(LuaError::RuntimeError(format!(
                 "invalid job declarations in module '{}', declaration count doesn't match the registration count",
@@ -55,15 +49,20 @@ impl VivyJobRegister {
 
         Ok(())
     }
+
+    /// Iterate over all declared jobs.
+    pub(crate) fn iter_declared(&self) -> std::slice::Iter<Arc<RwLock<VivyJob>>> {
+        self.all_declared_jobs.iter()
+    }
 }
 
 impl LuaUserData for VivyJobRegister {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_meta_function("_name", |lua, ()| lua.create_string("VivyJobRegister"));
+
         methods.add_method_mut("will_register", |_, this, name: String| -> LuaResult<()> {
             if this.job_names.contains(&name) {
-                Err(LuaError::RuntimeError(format!(
-                    "redefinition of job '{name}'"
-                )))
+                Err(LuaError::RuntimeError(format!("redefinition of job '{name}'")))
             } else {
                 this.job_names.insert(name);
                 Ok(())
@@ -80,20 +79,14 @@ impl LuaUserData for VivyJobRegister {
                         "try to register job '{name}' but it was not declared"
                     )));
                 } else if this.local_jobs.contains_key(name) {
-                    return Err(LuaError::RuntimeError(format!(
-                        "try to register job '{name}' again"
-                    )));
+                    return Err(LuaError::RuntimeError(format!("try to register job '{name}' again")));
                 }
 
-                let declaration = Rc::new(RefCell::new(VivyJob {
-                    module: match lua
-                        .load(chunk!(return vivy: ___current_module()))
-                        .set_name("query current module name")?
-                        .eval::<LuaValue>()
-                    {
-                        Ok(LuaValue::String(str)) => Some(str.to_string_lossy().to_string()),
-                        _ => None,
-                    },
+                let declaration = Arc::new(RwLock::new(VivyJob {
+                    module: lua
+                        .load(chunk! { vivy:___current_module() })
+                        .eval::<LuaString>()
+                        .map(|str| str.to_string_lossy().to_string())?,
                     name: name.to_string(),
                     doc: table.doc,
                     function: lua.create_registry_value(table.function)?.into(),
@@ -101,8 +94,7 @@ impl LuaUserData for VivyJobRegister {
                     return_type: table.return_type,
                 }));
 
-                this.local_jobs
-                    .insert(name.to_string(), declaration.clone());
+                this.local_jobs.insert(name.to_string(), declaration.clone());
                 this.all_declared_jobs.push(declaration);
 
                 Ok(())
diff --git a/src/Rust/vvs_lua/src/lib.rs b/src/Rust/vvs_lua/src/lib.rs
index e649a844..d752d5ee 100644
--- a/src/Rust/vvs_lua/src/lib.rs
+++ b/src/Rust/vvs_lua/src/lib.rs
@@ -23,5 +23,5 @@ mod values;
 pub(crate) mod dsl;
 pub(crate) mod lua_wrapper;
 
-pub use functions::{get_loaded, load_user_script, print_info, setup};
+pub use functions::{load_user_script, print_info, setup};
 pub use toml_option::TomlOptions;
diff --git a/src/Rust/vvs_lua/src/libs/bit.rs b/src/Rust/vvs_lua/src/libs/bit.rs
new file mode 100644
index 00000000..fe559385
--- /dev/null
+++ b/src/Rust/vvs_lua/src/libs/bit.rs
@@ -0,0 +1,114 @@
+//! Provides a bits implementation in Rust. We supports most of the LuaJIT's bit module, but use
+//! i64 as the backing storage type, but all operations are unsigned (be carefull with the sign bit).
+
+use mlua::prelude::*;
+use std::mem::transmute;
+
+/// Try to get a bit representation from the [LuaValue]. For integer numbers we transmute them into
+/// [u64] instead of an [i64], it is safe to do so because they have the same storage size which is
+/// of 64 bits.
+fn get_bits(lua: &Lua, value: LuaValue) -> LuaResult<u64> {
+    match value {
+        LuaNil | LuaValue::Boolean(false) => Ok(0),
+        LuaValue::Boolean(true) => Ok(1),
+        LuaValue::Integer(integer) => Ok(unsafe { transmute::<i64, u64>(integer) }),
+        LuaValue::Number(number) => Ok(f64::to_bits(number)),
+        _ => Ok(unsafe { transmute::<i64, u64>(lua.coerce_integer(value)?.unwrap_or_default()) }),
+    }
+}
+
+/// Converts the unsigned integer back into a [LuaValue].
+fn from_bits(value: u64) -> LuaResult<LuaValue<'static>> {
+    Ok(LuaValue::Integer(unsafe { transmute::<u64, i64>(value) }))
+}
+
+crate::libs::required! { lua => {
+    let module = lua.create_table()?;
+
+    module.raw_set("tobit", lua.create_function(|lua, value| {
+        from_bits(get_bits(lua, value)?)
+    })?)?;
+
+    module.raw_set("tohex", lua.create_function(|lua, (value, n): (LuaValue, Option<i64>)| {
+        let value = get_bits(lua, value)?;
+        match n {
+            Some(n) if n >= 0 => {
+                let n: usize = n as usize;
+                lua.create_string(&format!("{value:0n$x}"))
+            },
+            Some(n) => {
+                let n: usize = (-n) as usize;
+                lua.create_string(&format!("{value:0n$X}"))
+            }
+            None => lua.create_string(&format!("{value:x}")),
+        }
+    })?)?;
+
+    module.raw_set("bnot", lua.create_function(|lua, value| {
+        from_bits(u64::wrapping_neg(get_bits(lua, value)?))
+    })?)?;
+
+    module.raw_set("bor", lua.create_function(|lua, values: LuaMultiValue| {
+        let mut ret = 0;
+        for value in values {
+            ret |= get_bits(lua, value)?;
+        }
+        from_bits(ret)
+    })?)?;
+
+    module.raw_set("band", lua.create_function(|lua, mut values: LuaMultiValue| {
+        match values.pop_front() {
+            None => Err(LuaError::RuntimeError("you may pass at least one argument to the 'binary and' function".to_string())),
+            Some(ret) => {
+                let mut ret = get_bits(lua, ret)?;
+                for value in values {
+                    ret &= get_bits(lua, value)?;
+                }
+                from_bits(ret)
+            }
+        }
+    })?)?;
+
+    module.raw_set("bxor", lua.create_function(|lua, mut values: LuaMultiValue| {
+        match values.pop_front() {
+            None => Err(LuaError::RuntimeError("you may pass at least one argument to the 'binary xor' function".to_string())),
+            Some(ret) => {
+                let mut ret = get_bits(lua, ret)?;
+                for value in values {
+                    ret ^= get_bits(lua, value)?;
+                }
+                from_bits(ret)
+            }
+        }
+    })?)?;
+
+    module.raw_set("lshift", lua.create_function(|lua, (value, n): (LuaValue, u32)| {
+        from_bits(u64::wrapping_shl(get_bits(lua, value)?, n))
+    })?)?;
+
+    module.raw_set("rshift", lua.create_function(|lua, (value, n): (LuaValue, u32)| {
+        from_bits(u64::wrapping_shr(get_bits(lua, value)?, n))
+    })?)?;
+
+    module.raw_set("arshift", lua.create_function(|lua, (value, n): (LuaValue, u32)| {
+        Ok(LuaValue::Integer(i64::wrapping_shr(
+            unsafe { transmute::<u64, i64>(get_bits(lua, value)?) },
+            n
+        )))
+    })?)?;
+
+    module.raw_set("bswap", lua.create_function(|lua, value: LuaValue| {
+        from_bits(u64::swap_bytes(get_bits(lua, value)?))
+    })?)?;
+
+    module.raw_set("rol", lua.create_function(|lua, (value, n): (LuaValue, u32)| {
+        from_bits(u64::rotate_left (get_bits(lua, value)?, n))
+    })?)?;
+
+    module.raw_set("ror", lua.create_function(|lua, (value, n): (LuaValue, u32)| {
+        from_bits(u64::rotate_right(get_bits(lua, value)?, n))
+    })?)?;
+
+    crate::dsl::into_readonly_table(lua, module)
+        .map(LuaValue::Table)
+}}
diff --git a/src/Rust/vvs_lua/src/libs/hashset.rs b/src/Rust/vvs_lua/src/libs/hashset.rs
index 0ed0570d..f3477473 100644
--- a/src/Rust/vvs_lua/src/libs/hashset.rs
+++ b/src/Rust/vvs_lua/src/libs/hashset.rs
@@ -5,14 +5,9 @@ use mlua::{chunk, prelude::*};
 crate::libs::required! { "hashset.lua": chunk! {
     local HashSet = {}
 
-    function HashSet.print(s)
-        if type(s) ~= "table" then error("expected a table") end
-        print(HashSet.tostring(s))
-    end
-
     HashSet.mt = {
         __add = function (a, b)
-            if getmetatable(a) ~= "hashset" or getmetatable(b) ~= "set" then
+            if getmetatable(a) ~= "hashset" or getmetatable(b) ~= "hashset" then
                 error("attempt to `add' a hashset with a non-set value", 2)
             end
             local res = HashSet.new {}
@@ -22,7 +17,7 @@ crate::libs::required! { "hashset.lua": chunk! {
         end,
 
         __mul = function (a, b)
-            if getmetatable(a) ~= "hashset" or getmetatable(b) ~= "set" then
+            if getmetatable(a) ~= "hashset" or getmetatable(b) ~= "hashset" then
                 error("attempt to `mul' a hashset with a non-set value", 2)
             end
             local res = HashSet.new {}
@@ -33,7 +28,7 @@ crate::libs::required! { "hashset.lua": chunk! {
         end,
 
         __tostring = function(hashset)
-            if type(hashset) ~= "table" then error("expected a table") end
+            if type(hashset) ~= "hashset" then error("expected a hashset") end
             local s = "{"
             local sep = ""
             for e in pairs(hashset) do
@@ -44,14 +39,14 @@ crate::libs::required! { "hashset.lua": chunk! {
         end,
 
         __index     = function (s, k) return rawget(s, k) ~= nil end,
-        __newindex  = function (s, k, _) rawhashset(s, k, true) end,
+        __newindex  = function (s, k, _) rawset(s, k, true) end,
         __metatable = "hashset",
     }
 
     function HashSet.new(t)
         if type(t) ~= "table" then error("expected a table") end
         local hashset = {}
-        hashsetmetatable(set, HashSet.mt)
+        setmetatable(hashset, HashSet.mt)
         for _, l in ipairs(t) do hashset[l] = true end
         return hashset
     end
diff --git a/src/Rust/vvs_lua/src/libs/mod.rs b/src/Rust/vvs_lua/src/libs/mod.rs
index 75ebb29d..faaed40d 100644
--- a/src/Rust/vvs_lua/src/libs/mod.rs
+++ b/src/Rust/vvs_lua/src/libs/mod.rs
@@ -1,10 +1,11 @@
 //! Expose functionalities to VivyScript as modules that can be imported.
 
+mod bit;
 mod hashset;
 mod rectangle;
-mod vivy;
+pub(super) mod vivy;
 
-pub use self::vivy::{Vivy, VivyPtr};
+pub use self::vivy::Vivy;
 use mlua::{chunk, prelude::*};
 
 macro_rules! required {
@@ -22,9 +23,18 @@ macro_rules! required {
 }
 pub(self) use required;
 
-pub fn import(lua: &Lua, name: LuaString) -> LuaResult<()> {
-    let name = core::str::from_utf8(name.as_bytes())
-        .map_err(|err| LuaError::RuntimeError(format!("{err}")))?;
+pub fn import(lua: &Lua, (name, table): (LuaString, LuaValue)) -> LuaResult<()> {
+    let name = core::str::from_utf8(name.as_bytes()).map_err(|err| LuaError::RuntimeError(format!("{err}")))?;
+    log::debug!(target: "lua", "trying to import module '{name}'");
+    let table = match table {
+        LuaValue::Boolean(_) | LuaValue::Table(_) => table,
+        _ => {
+            return Err(LuaError::RuntimeError(format!(
+                "expected nil or a table, got: {}",
+                table.type_name()
+            )))
+        }
+    };
 
     if lua
         .load(chunk! { return vivy ~= nil and vivy:___is_already_imported($name) })
@@ -67,7 +77,7 @@ pub fn import(lua: &Lua, name: LuaString) -> LuaResult<()> {
 
     lua.load(chunk! {
         if $lib ~= nil then
-            rawset(_G, $name, $lib)
+            rawset(not $table and _G or $table, $name, $lib)
         end
         if vivy ~= nil then
             vivy:___mark_loaded($name)
diff --git a/src/Rust/vvs_lua/src/libs/vivy.rs b/src/Rust/vvs_lua/src/libs/vivy.rs
index a738639c..3bc5559c 100644
--- a/src/Rust/vvs_lua/src/libs/vivy.rs
+++ b/src/Rust/vvs_lua/src/libs/vivy.rs
@@ -1,662 +1,13 @@
 //! Provides the vivy runtime.
 
-use crate::{
-    data::VivyDataRegisterPtr,
-    dsl,
-    func::VivyFuncRegisterPtr,
-    jobs::{VivyCalledJob, VivyJobRegisterPtr},
-    lua_wrapper::{
-        LuaAssContainerPtr, LuaAssLinePtr, LuaAssLinesPtr, LuaAssSyllabePtr, LuaAssSyllabesPtr,
-        LuaAssType,
-    },
-    options::VivyOptionRegisterPtr,
-};
-use mlua::{chunk, prelude::*};
-use std::{
-    cell::RefCell,
-    collections::{HashMap, HashSet},
-    path::PathBuf,
-    rc::Rc,
-    sync::Arc,
-};
-use vvs_ass::{ASSAuxTablePtr, ASSLine, ASSLines, ASSSyllabe, ASSSyllabes, ASSType};
-use vvs_utils::either;
+mod byte_code;
+mod graph;
+mod runtime;
 
-#[derive(Default, Debug)]
-pub struct Vivy {
-    options: Option<VivyOptionRegisterPtr>,
-    data: Option<VivyDataRegisterPtr>,
-    jobs: Option<VivyJobRegisterPtr>,
-    func: Option<VivyFuncRegisterPtr>,
-    path: Vec<String>,
+use mlua::prelude::*;
+pub use runtime::Vivy;
 
-    variables: HashMap<String, LuaRegistryKey>,
-    is_in_job_evaluation: bool,
-    should_print_infos: bool,
-    main_once: bool,
-
-    loaded: HashSet<String>,
-    loading: HashSet<String>,
-    current_module: Option<String>,
-}
-
-pub type VivyPtr = Rc<RefCell<Vivy>>;
-
-#[derive(Debug)]
-struct VivyExecGraph {
-    nodes: HashSet<String>,
-    edges: Vec<(String, Vec<String>, Rc<LuaRegistryKey>)>,
-    exits: Vec<String>,
-}
-
-macro_rules! if_let_else_chain {
-    (@default: $default_expr: expr) => {{ $default_expr }};
-
-    ($var: ident <- $expr: expr => $var_expr: expr;
-     @default: $default_expr: expr
-    ) => {
-        if let Ok($var) = $expr {
-            $var_expr
-        } else {
-            $default_expr
-        }
-    };
-
-    ($var_first: ident <- $expr_first: expr => $var_expr_first: expr;
-     $($var: ident <- $expr: expr => $var_expr: expr);+;
-     @default: $default_expr: expr
-    ) => {
-        if let Ok($var_first) = $expr_first {
-            $var_expr_first
-        } else { if_let_else_chain! {
-            $($var <- $expr => $var_expr);+;
-            @default: $default_expr
-        } }
-    };
-}
-
-/// See [Vivy::execute_job] for an example on how to use this macro.
-macro_rules! match_take {
-    ($expr: expr =>
-     $(; $var: ident: $ty: ident => $var_expr: expr)+
-     $(; @default => $default_expr: expr)?
-    ) => {{
-        if_let_else_chain! {
-            $($var <- $expr.take::<$ty>() => $var_expr);+;
-            $(@default: $default_expr)?
-        }
-    }};
-}
-
-impl VivyExecGraph {
-    pub fn insert(
-        &mut self,
-        destination: impl ToString,
-        function: Rc<LuaRegistryKey>,
-        src: impl IntoIterator<Item = impl ToString>,
-    ) -> bool {
-        let dest = destination.to_string();
-        let src: Vec<String> = src.into_iter().map(|str| str.to_string()).collect();
-        log::debug!(target: "lua", "{dest:?} <--({function:?})--- {src:?}");
-        if src.iter().filter(|src| either!(self.nodes.contains(src.as_str()) => false; {
-            log::error!(target: "lua", "source variable '{src}' is not present in the already assigned set");
-            true
-        })).count().ne(&0) {
-            return false;
-        } else if !self.nodes.insert(destination.to_string()) {
-            log::error!(target: "lua", "re-assignation of variable '{dest}' detected");
-            return false;
-        }
-        self.edges.push((dest, src, function));
-        true
-    }
-}
-
-impl Default for VivyExecGraph {
-    fn default() -> Self {
-        Self {
-            nodes: HashSet::from(["INIT".to_string()]),
-            edges: Default::default(),
-            exits: Default::default(),
-        }
-    }
-}
-
-impl<'lua, I> TryFrom<(&'lua Lua, I, LuaValue<'lua>)> for VivyExecGraph
-where
-    I: IntoIterator<Item = (usize, Option<LuaString<'lua>>, LuaValue<'lua>)>,
-{
-    type Error = LuaError;
-
-    fn try_from((lua, work, ret): (&'lua Lua, I, LuaValue<'lua>)) -> Result<Self, Self::Error> {
-        let mut graph = Self {
-            exits: dsl::into_string_vec(lua, ret)?,
-            ..Default::default()
-        };
-        for (idx, dest, src) in work {
-            let dest = dest.ok_or(LuaError::RuntimeError("".to_string()))?;
-            let dest = dest.to_string_lossy();
-            let src = match src {
-                LuaValue::UserData(src) if src.is::<VivyCalledJob>() => {
-                    src.take::<VivyCalledJob>().expect("very internal error")
-                }
-                _ => return Err(LuaError::RuntimeError("".to_string())),
-            };
-            if !graph.insert(&dest, src.function, &src.arguments) {
-                return Err(LuaError::RuntimeError(format!(
-                    "invalid instruction n°{} in main block, edge was: '{dest:?} <- {:?}'",
-                    idx, src.arguments
-                )));
-            }
-        }
-        let invalids = graph.exits.iter().filter(|exit| {
-            either!(graph.nodes.contains(exit.as_str()) => false; {
-                log::error!(target: "lua", "invalid exit node '{exit}', variable was never assigned");
-                true
-            })
-        });
-        if invalids.count().ne(&0) {
-            Err(LuaError::RuntimeError(format!(
-                "invalid exit nodes for execution graph: {graph:#?}"
-            )))
-        } else {
-            Ok(graph)
-        }
-    }
-}
-
-impl Vivy {
-    /// Create a new instance of the vivy module, with a specified include path.
-    pub fn new_with_path(path: impl IntoIterator<Item = impl AsRef<str>>) -> VivyPtr {
-        Rc::new(RefCell::new(Self {
-            path: path
-                .into_iter()
-                .map(|str| str.as_ref().to_string())
-                .collect(),
-            ..Default::default()
-        }))
-    }
-
-    /// Create a new default ASS element of the asked type.
-    pub fn new_ass_element<'lua>(&self, lua: &'lua Lua, ty: ASSType) -> LuaResult<LuaValue<'lua>> {
-        macro_rules! new {
-            ($elem: ident) => {
-                paste::paste! {{
-                    let elem = [< ASS $elem >] ::default().into_ptr();
-                    elem.borrow_mut().aux = self.new_ass_aux_table(ty);
-                    Ok( [< LuaAss $elem Ptr >] ::from(elem).to_lua(lua)?)
-                }}
-            };
-        }
-        match ty {
-            ASSType::Lines => new!(Lines),
-            ASSType::Line => new!(Line),
-            ASSType::Syllabes => new!(Syllabes),
-            ASSType::Syllabe => new!(Syllabe),
-        }
-    }
-
-    /// Create a new default ASS element aux table of the asked type. The table is filled with the
-    /// default options for the said element.
-    pub fn new_ass_aux_table(&self, ty: ASSType) -> ASSAuxTablePtr {
-        self.data
-            .as_ref()
-            .map(|data| data.borrow().get_table(ty))
-            .unwrap_or_default()
-            .into_inner()
-    }
-
-    /// Pack the arguments for a job, check for correct type, coerce if needed, etc.
-    fn pack_job_arguments<'lua>(
-        &self,
-        lua: &'lua Lua,
-        input: ASSType,
-        args: Vec<LuaString<'lua>>,
-    ) -> LuaResult<Vec<LuaValue<'lua>>> {
-        let (vars, errs): (Vec<_>, Vec<_>) = args
-            .into_iter()
-            .map(|arg| match self.variables.get(arg.to_str()?) {
-                Some(var) => match lua.registry_value::<LuaValue>(var)? {
-                    var @ LuaValue::UserData(_) => Ok(var),
-                    var => Err(LuaError::RuntimeError(format!(
-                        "invalid type '{}' for job argument '{}'",
-                        var.type_name(),
-                        arg.to_str()?
-                    ))),
-                },
-                None => Err(LuaError::RuntimeError(format!(
-                    "failed to find variable '{}' in execution state",
-                    arg.to_str()?
-                ))),
-            })
-            .partition(Result::is_ok);
-        if let Some(Err(err)) = errs.into_iter().next() {
-            return Err(err);
-        }
-        let mut args: Vec<LuaValue> = Default::default();
-        for var in vars.into_iter().map(Result::unwrap) {
-            let LuaValue::UserData(var) = var else {
-                panic!("we should have a user data type here, got {}", var.type_name())
-            };
-            match input {
-                ASSType::Lines | ASSType::Line => match_take! { var =>
-                    ; var: LuaAssLinePtr  => { args.push(var.to_lua(lua)?); continue }
-                    ; var: LuaAssLinesPtr => {
-                        for var in &var.into_inner().borrow().content {
-                            args.push(LuaAssLinePtr::from(var.clone()).to_lua(lua)?);
-                        }
-                        continue
-                    }
-                    ; var: LuaAssContainerPtr => {
-                        for var in &var.into_inner().borrow().lines.borrow().content {
-                            args.push(LuaAssLinePtr::from(var.clone()).to_lua(lua)?);
-                        }
-                        continue
-                    }
-                    ; @default => { return Err(LuaError::RuntimeError(
-                        "expected a line or lines, got syllabe or syllabes".to_string()
-                    )) }
-                },
-                ASSType::Syllabes | ASSType::Syllabe => match_take! { var =>
-                    ; var: LuaAssSyllabePtr  => { args.push(var.to_lua(lua)?); continue }
-                    ; var: LuaAssSyllabesPtr => {
-                        for var in &var.into_inner().borrow().content {
-                            args.push(LuaAssSyllabePtr::from(var.clone()).to_lua(lua)?);
-                        }
-                        continue
-                    }
-                    ; var: LuaAssLinePtr => {
-                        for var in &var.into_inner().borrow().content.borrow().content {
-                            args.push(LuaAssSyllabePtr::from(var.clone()).to_lua(lua)?);
-                        }
-                        continue
-                    }
-                    ; var: LuaAssLinesPtr => {
-                        for line in var.into_inner().borrow().content.iter() {
-                            for var in &line.borrow().content.borrow().content {
-                                args.push(LuaAssSyllabePtr::from(var.clone()).to_lua(lua)?);
-                            }
-                        }
-                        continue
-                    }
-                    ; var: LuaAssContainerPtr => {
-                        for line in var.into_inner().borrow().lines.borrow().content.iter() {
-                            for var in &line.borrow().content.borrow().content {
-                                args.push(LuaAssSyllabePtr::from(var.clone()).to_lua(lua)?);
-                            }
-                        }
-                        continue
-                    }
-                    ; @default => { return Err(LuaError::RuntimeError(
-                        "expected a syllabe or syllabes, got line or lines".to_string()
-                    )) }
-                },
-            };
-        }
-        match input {
-            ASSType::Lines | ASSType::Syllabes => {
-                log::debug!(target: "lua", "we already have flattened the input elements");
-                Ok(args)
-            }
-            ASSType::Line | ASSType::Syllabe => {
-                log::debug!(target: "lua", "we need to pack the flattened elements into a parent element");
-                let mut elem = self.new_ass_element(lua, input)?;
-                for arg in args {
-                    elem = lua
-                        .load(chunk! { local elem = $elem; elem.push($arg); return elem })
-                        .eval()?;
-                }
-                Ok(vec![elem])
-            }
-        }
-    }
-
-    fn unpack_job_returns<'lua>(
-        &self,
-        lua: &'lua Lua,
-        name: &str,
-        returns: ASSType,
-        output: impl Iterator<Item = LuaValue<'lua>>,
-    ) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
-        fn unwrap(var: LuaValue) -> LuaAnyUserData {
-            match var {
-                LuaValue::UserData(var) => var,
-                _ => unreachable!(),
-            }
-        }
-
-        fn handle_user_data<'lua>(
-            lua: &'lua Lua,
-            returns: ASSType,
-            var: LuaAnyUserData<'lua>,
-        ) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
-            match returns {
-                ASSType::Lines | ASSType::Line => match_take! { var =>
-                    ; var: LuaAssLinePtr  => Ok(vec![unwrap(var.to_lua(lua)?)])
-                    ; var: LuaAssLinesPtr => Ok(var.into_inner().borrow().content.iter().map(|line|
-                        unwrap(LuaAssLinePtr::from(line.clone()).to_lua(lua).unwrap())
-                    ).collect())
-                    ; @default => Err(LuaError::RuntimeError(
-                        "expected a line or lines, got syllabe or syllabes".to_string()
-                    ))
-                },
-                ASSType::Syllabes | ASSType::Syllabe => match_take! { var =>
-                    ; var: LuaAssSyllabePtr  => Ok(vec![unwrap(var.to_lua(lua)?)])
-                    ; var: LuaAssSyllabesPtr => Ok(var.into_inner().borrow().content.iter().map(|line|
-                        unwrap(LuaAssSyllabePtr::from(line.clone()).to_lua(lua).unwrap())
-                    ).collect())
-                    ; @default => Err(LuaError::RuntimeError(
-                        "expected a syllabe or syllabes, got line or lines".to_string()
-                    ))
-                },
-            }
-        }
-
-        let (output, errs): (Vec<_>, Vec<_>) = output
-            .map(|out: LuaValue<'lua>| match out {
-                LuaValue::UserData(out) => handle_user_data(lua, returns, out),
-                LuaValue::Table(outs) => {
-                    let (outs, errs): (Vec<_>, Vec<_>) = outs
-                        .sequence_values()
-                        .map(|item| handle_user_data(lua, returns, item?))
-                        .partition(Result::is_ok);
-                    if let Some(Err(err)) = errs.into_iter().next() {
-                        return Err(err);
-                    }
-                    Ok(outs.into_iter().flat_map(Result::unwrap).collect())
-                }
-                _ => Err(LuaError::RuntimeError(format!(
-                    "invalid return type for job '{name}', got a value of type: {}",
-                    out.type_name()
-                ))),
-            })
-            .partition(Result::is_ok);
-        if let Some(Err(err)) = errs.into_iter().next() {
-            return Err(err);
-        }
-        let (output, errs): (Vec<_>, Vec<_>) = output.into_iter().partition(Result::is_ok);
-        if let Some(Err(err)) = errs.into_iter().next() {
-            return Err(err);
-        }
-        let output = output.into_iter().flat_map(Result::unwrap).collect();
-
-        match returns {
-            ASSType::Line | ASSType::Syllabe => Ok(output),
-            ty @ ASSType::Lines | ty @ ASSType::Syllabes => {
-                let mut elem = self.new_ass_element(lua, ty)?;
-                for out in output {
-                    elem = lua
-                        .load(chunk! {
-                            local elem = $elem
-                            elem.push($out)
-                            elem
-                        })
-                        .eval()?;
-                }
-                Ok(vec![unwrap(elem)])
-            }
-        }
-    }
-
-    /// Execute a job with the named arguments. The types of the arguments will be checked and it
-    /// will be up to this function to create the apply loop if needed. The returned values will
-    /// also be flattened if needed.
-    fn execute_job<'lua>(
-        &mut self,
-        lua: &'lua Lua,
-        name: &str,
-        job: LuaFunction<'lua>,
-        input: ASSType,
-        returns: ASSType,
-        args: Vec<LuaString<'lua>>,
-    ) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
-        let (output, errs): (Vec<_>, Vec<_>) = self
-            .pack_job_arguments(lua, input, args)?
-            .into_iter()
-            .map(|arg| {
-                let ret = job.call::<LuaValue, LuaValue>(arg);
-                self.is_in_job_evaluation = true;
-                ret
-            })
-            .partition(Result::is_ok);
-        if let Some(Err(err)) = errs.into_iter().next() {
-            return Err(LuaError::CallbackError {
-                traceback: format!("error found while executing job '{name}'"),
-                cause: Arc::new(err),
-            });
-        }
-        self.unpack_job_returns(lua, name, returns, output.into_iter().map(Result::unwrap))
-    }
-}
-
-impl LuaUserData for Vivy {
-    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
-        macro_rules! add_setter {
-            ($n: ident: $p: ident;
-             $($name: ident: $ptr: ident);+$(;)?
-            ) => {
-                add_setter! { $n: $p }
-                add_setter! { $($name: $ptr);+ }
-            };
-
-            ($name: ident: $ptr: ident $(;)?) => {
-                methods.add_method_mut(concat!("___set_", stringify!($name)), |_, this, $name: $ptr| {
-                    this.$name = Some($name);
-                    Ok(())
-                });
-            };
-        }
-
-        macro_rules! add_getter {
-            ($n: literal: $t: ident => $e: expr;
-             $($name: literal: $this: ident => $expr: expr);+$(;)?
-            ) => {
-                add_getter! { $n: $t => $e }
-                add_getter! { $($name: $this => $expr);+ }
-            };
-
-            ($name: literal: $this: ident => $expr: expr $(;)?) => {
-                methods.add_method($name, |_, $this, ()| Ok($expr));
-            };
-        }
-
-        add_setter! {
-            options: VivyOptionRegisterPtr;
-            data: VivyDataRegisterPtr;
-            jobs: VivyJobRegisterPtr;
-            func: VivyFuncRegisterPtr;
-        }
-
-        add_getter! {
-            "___get_loaded":                this => this.loaded.clone();
-            "___get_path":                  this => this.path.clone();
-            "___is_in_job_evaluation_mode": this => this.is_in_job_evaluation;
-        }
-
-        methods.add_method_mut("___append_to_path", |_, this, pattern: String| {
-            this.path.push(pattern);
-            Ok(())
-        });
-
-        methods.add_method_mut("___mark_loaded", |_, this, module: String| {
-            this.loading.remove(&module);
-            Ok(())
-        });
-
-        methods.add_method_mut("___set_current_module", |_, this, module: LuaValue| {
-            this.current_module = match module {
-                LuaValue::String(module) => Some(module.to_string_lossy().to_string()),
-                _ => None,
-            };
-            Ok(())
-        });
-
-        methods.add_method("___current_module", |lua, this, ()| {
-            Ok(match this.current_module {
-                Some(ref current_module) => current_module.clone().to_lua(lua)?,
-                None => LuaValue::Nil,
-            })
-        });
-
-        methods.add_method("___run_export_module", |lua, this, name: LuaString| {
-            let table = lua.create_table()?;
-            this.jobs
-                .as_ref()
-                .map(|jobs| jobs.borrow_mut().export(lua, &name, &table))
-                .unwrap_or(Ok(()))?;
-            this.func
-                .as_ref()
-                .map(|func| func.borrow_mut().export(lua, &name, &table))
-                .unwrap_or(Ok(()))?;
-            let table = dsl::into_readonly_table(lua, name.clone(), table)?;
-            lua.globals().raw_set(name, table)?;
-
-            Ok(())
-        });
-
-        methods.add_method_mut(
-            "___execute_job",
-            |lua,
-             this,
-             (name, job, args, input, returns): (
-                LuaString,
-                LuaFunction,
-                Vec<LuaString>,
-                LuaAssType,
-                LuaAssType,
-            )| {
-                this.execute_job(
-                    lua,
-                    name.to_str()?,
-                    job,
-                    input.into_inner(),
-                    returns.into_inner(),
-                    args,
-                )
-            },
-        );
-
-        methods.add_method_mut("___import", |lua, this, name: String| -> LuaResult<bool> {
-            if this.loading.contains(&name) {
-                return Err(LuaError::RuntimeError(format!(
-                    "circular dependency found with module '{name}'"
-                )));
-            }
-            this.loading.insert(name.clone());
-            this.path
-                .iter()
-                .find_map(|path| {
-                    let path = PathBuf::from(path.replace('?', &name));
-                    let was_lua_file = path
-                        .extension()
-                        .map(|ext| ext.eq("lua"))
-                        .unwrap_or_default();
-                    (path.exists() && path.is_file()).then(|| {
-                        let path_name = path.to_string_lossy();
-                        match lua.load(&path).set_name(&path_name)?.exec() {
-                            Ok(()) => {
-                                this.loaded.insert(name.clone());
-                                Ok(was_lua_file)
-                            }
-                            Err(err) => Err(LuaError::RuntimeError(format!(
-                                "failed to load module '{name}' at location: {path_name}\n{err}"
-                            ))),
-                        }
-                    })
-                })
-                .unwrap_or(Err(LuaError::RuntimeError(format!(
-                    "failed to find or load module named '{name}' in path: {:#?}",
-                    this.path
-                ))))
-        });
-
-        methods.add_method_mut("___is_already_imported", |_, this, module: String| {
-            Ok(this.loaded.contains(&module))
-        });
-
-        methods.add_method_mut("___switch_to_execution_mode", |_, this, ()| {
-            this.is_in_job_evaluation
-                .then(|| {
-                    this.is_in_job_evaluation = false;
-                    Ok(())
-                })
-                .unwrap_or_else(|| {
-                    Err(LuaError::RuntimeError(
-                        "already in execution mode, internal error".to_string(),
-                    ))
-                })
-        });
-
-        methods.add_method_mut("___print_info", |_, this, ()| {
-            this.should_print_infos = true;
-            Ok(())
-        });
-
-        methods.add_method_mut("___main", |lua, this, table: LuaTable| -> LuaResult<()> {
-            if this.main_once {
-                return Err(LuaError::RuntimeError(
-                    "multiple 'main' blocks detected".to_string(),
-                ));
-            }
-
-            // Finish setup
-            if let Some(ref options) = this.options {
-                let options = options.borrow();
-                for option in options.iter_registered() {
-                    let value = match options.resolve(option) {
-                        Some(value) => value.to_lua(lua)?,
-                        None => LuaValue::Nil,
-                    };
-                    lua.globals().raw_set(option.as_str(), value)?;
-                }
-            }
-
-            if let Some(ref data) = this.data {
-                data.borrow_mut().compute_cached_tables()
-            }
-
-            // Print infos
-            if this.should_print_infos {
-                println!(" <<< Information Report >>>");
-                println!(" # Misc");
-                println!(
-                    "   - version    {}",
-                    lua.globals().get::<_, String>("_VERSION")?
-                );
-                println!("   - packages   {}", {
-                    let mut packages = this.loaded.iter().cloned().collect::<Vec<_>>();
-                    packages.sort();
-                    let packages = packages.join(", ");
-                    packages
-                });
-                if let Some(options) = this.options.as_ref() {
-                    options.borrow().print_info();
-                }
-                if let Some(data) = this.data.as_ref() {
-                    data.borrow().print_info();
-                }
-                this.should_print_infos = false;
-            }
-
-            // Do the work
-            this.is_in_job_evaluation = true;
-            let table_last_idx = table.raw_len() - 1;
-            let (work, ret): (Vec<_>, Vec<_>) = table
-                .pairs::<LuaValue, LuaValue>()
-                .enumerate()
-                .flat_map(|(idx, pair)| {
-                    let (dest, src) = pair.ok()?;
-                    Some((idx, lua.coerce_string(dest).unwrap_or_default(), src))
-                })
-                .partition(|(idx, ..)| i64::try_from(*idx).unwrap() != table_last_idx);
-            let [(_, _, ret)] = &ret[..] else { unreachable!("invalid return statements, should only be one: {ret:#?}") };
-            let exec_graph = VivyExecGraph::try_from((lua, work, ret.clone()))?;
-            log::error!(target: "lua", "implement execution logic for: {exec_graph:#?}");
-            this.is_in_job_evaluation = false;
-            Ok(())
-        });
-    }
-}
+pub(crate) use runtime::{ASSFileOrInstance, RuntimeCreationData, RuntimeMode, RuntimeOptions};
 
 crate::libs::required! { lua => lua
     .create_userdata(Vivy::new_with_path(["./?.vvl", "./?.lua"]))
diff --git a/src/Rust/vvs_lua/src/libs/vivy/byte_code.rs b/src/Rust/vvs_lua/src/libs/vivy/byte_code.rs
new file mode 100644
index 00000000..10cb96d4
--- /dev/null
+++ b/src/Rust/vvs_lua/src/libs/vivy/byte_code.rs
@@ -0,0 +1,331 @@
+use crate::{
+    func::VivyFunc,
+    jobs::VivyJob,
+    lua_wrapper::{LuaAssAuxValue, LuaAssType},
+};
+use mlua::{chunk, prelude::*};
+use serde::{Deserialize, Serialize};
+use std::{
+    collections::HashMap,
+    path::{Path, PathBuf},
+    str::FromStr,
+    sync::Arc,
+    time::SystemTime,
+};
+
+/// The bytecode of a function, with its up-values.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub(super) struct FunctionByteCode {
+    content: Vec<u8>,
+    upvalues: HashMap<i64, FunctionByteCode>,
+}
+
+/// The name of an exported symbol.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
+pub(super) struct SymbolName {
+    pub module: String,
+    pub name: String,
+}
+
+/// An exported symbol.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub(super) enum Symbol {
+    Function {
+        bytecode: FunctionByteCode,
+    },
+
+    Job {
+        bytecode: FunctionByteCode,
+        input_type: LuaAssType,
+        output_type: LuaAssType,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub(super) struct Module {
+    pub name: String,
+    pub path: Option<PathBuf>,
+    pub mtime: Option<SystemTime>,
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ScriptByteCode {
+    /// The path to the main vivy script file.
+    pub(super) path: PathBuf,
+
+    /// The last modification time of the vivy script file, at the time this whole structure was
+    /// created.
+    pub(super) mtime: Option<SystemTime>,
+
+    /// List of imported modules, the import order is not relevent, they will be re-imported in
+    /// other lua states if needed.
+    pub(super) imports: Vec<Module>,
+
+    /// List of callable symbols.
+    pub(super) symbols: HashMap<SymbolName, Symbol>,
+
+    /// Stores the options and constants that must registered in the global state in an imutable
+    /// way.
+    pub(super) globals: HashMap<String, LuaAssAuxValue>,
+
+    /// Stores the auxiliary data on ASS types and their default values.
+    pub(super) data: Vec<(String, LuaAssType, LuaAssAuxValue)>,
+}
+
+impl FunctionByteCode {
+    pub fn try_new(
+        getters @ (upvalues_getter, func_bc_dump): (&LuaRegistryKey, &LuaRegistryKey),
+        lua: &Lua,
+        func: LuaFunction,
+    ) -> LuaResult<Self> {
+        let (upvalues_getter, func_bc_dump) = (
+            lua.registry_value::<LuaFunction>(upvalues_getter)?,
+            lua.registry_value::<LuaFunction>(func_bc_dump)?,
+        );
+        let (content, upvalues): (LuaString, LuaTable) = lua
+            .load(chunk! { return $func_bc_dump($func), $upvalues_getter($func) })
+            .eval()?;
+        Ok(Self {
+            content: content.as_bytes().to_vec(),
+            upvalues: HashMap::from_iter(upvalues.pairs::<i64, LuaFunction>().filter_map(|pair| {
+                let (idx, function) = pair.ok()?;
+                Some((idx, Self::try_new(getters, lua, function).ok()?))
+            })),
+        })
+    }
+}
+
+impl FromStr for SymbolName {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.split_once('.') {
+            Some((module, name)) if !module.is_empty() && !name.is_empty() => {
+                Ok(Self { module: module.to_string(), name: name.to_string() })
+            }
+            Some(_) => Err(format!("invalid symbol name found: {s}")),
+            None => Err(format!("can't find the module name in the exported symbol: {s}")),
+        }
+    }
+}
+
+impl std::fmt::Display for SymbolName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}.{}", self.module, self.name)
+    }
+}
+
+impl Module {
+    /// Create a module entry from a path, this is an external module.
+    pub fn from_file(name: impl ToString, path: impl Into<PathBuf>) -> Self {
+        let path = path.into();
+        Self {
+            name: name.to_string(),
+            mtime: std::fs::metadata(&path)
+                .map(|mt| mt.modified().ok())
+                .unwrap_or_default(),
+            path: Some(path),
+        }
+    }
+
+    /// Create a module entry from just a name, this is an internal module.
+    pub fn from_internal(name: impl ToString) -> Self {
+        Self { name: name.to_string(), path: None, mtime: None }
+    }
+
+    /// Get the path of the module if it is an external one.
+    pub fn path(&self) -> Option<&Path> {
+        self.path.as_ref().map(|path| path.as_ref())
+    }
+
+    /// Get the name of the module.
+    #[allow(dead_code)]
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    /// Get the modification time of the file used to build this module. Note that internal modules
+    /// don't have modification times and even if the module is external it may not have a
+    /// modification time if the platform doesn't support modification times...
+    pub fn mtime(&self) -> Option<SystemTime> {
+        self.mtime
+    }
+
+    /// Was the module a lua file? Can be true only if this is an external module.
+    pub fn was_lua_file(&self) -> bool {
+        self.path
+            .as_ref()
+            .map(|path| path.extension().map(|ext| ext.eq("runtime")).unwrap_or_default())
+            .unwrap_or_default()
+    }
+
+    /// Was the module a vivy file? Vivy files can only be external ones and we can only parse vivy
+    /// or lua files.
+    pub fn was_vivy_file(&self) -> bool {
+        self.is_external() && !self.was_lua_file()
+    }
+
+    /// Is the module an internal one?
+    pub fn is_internal(&self) -> bool {
+        !self.is_external()
+    }
+
+    /// Is the module an external one?
+    pub fn is_external(&self) -> bool {
+        self.path.is_some()
+    }
+
+    /// Tells whether the byte code is up to date with the file it was generated from. The rules
+    /// are the following:
+    /// - if the byte code has no storage file, then it is an internal module, we consider that we
+    ///   have ABI compatibility (this is a bold statement that will break at some point).
+    /// - if the byte code has no mtime, we can't check so we need to recompute the bytecode.
+    /// - if we have a mtime and a path that have a valide mtime, we check if the file was modified
+    ///   after the production of the byte code, if it's not the case we don't have to recompute
+    ///   the byte code. In any other case we must recompute the byte code.
+    pub fn was_modified(&self) -> bool {
+        let ret = match (self.path(), self.mtime()) {
+            (Some(path), Some(mtime)) => matches!(std::fs::metadata(path),
+                Ok(mt) if matches!(mt.modified(), Ok(fmtime) if fmtime > mtime)),
+            _ => false,
+        };
+        if ret {
+            log::error!(target: "runtime", "module '{}' was modified", self.name);
+        }
+        ret
+    }
+}
+
+impl ScriptByteCode {
+    /// Same logic as [Module::was_modified]. But we consider the script byte code outdated if any
+    /// of its dependency changed.
+    pub fn was_modified(&self) -> bool {
+        let main_was_modified = matches!((std::fs::metadata(&self.path), self.mtime),
+            (Ok(mt), Some(mtime)) if matches!(mt.modified(), Ok(fmtime) if fmtime > mtime),
+        );
+        if main_was_modified {
+            log::error!(target: "runtime", "main module '{}' was modified", self.path.to_string_lossy());
+        }
+        let imports_where_modified = self.imports.iter().any(|module| module.was_modified());
+        main_was_modified || imports_where_modified
+    }
+}
+
+impl<'lua> ToLua<'lua> for FunctionByteCode {
+    fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
+        let Self { content, upvalues } = self;
+        let (set_upvalue, loadstring): (LuaFunction, LuaFunction) = (
+            lua.named_registry_value("_set_upvalue")?,
+            lua.named_registry_value("_loadstring")?,
+        );
+        let content = lua.create_string(&content)?;
+        let content: LuaFunction = lua.load(chunk! { $loadstring($content) }).eval()?;
+        for (idx, bytecode) in upvalues {
+            set_upvalue.call((content.clone(), idx, bytecode.to_lua(lua)?))?;
+        }
+        Ok(LuaValue::Function(content))
+    }
+}
+
+impl<'lua> ToLua<'lua> for ScriptByteCode {
+    /// The script bytecode will be loaded as a table, where each item in the table will be one
+    /// module where the key is the name of the module.
+    ///
+    /// In addition to the modules, we have:
+    /// - The globals (options, constants, etc) will be in the special field `_CONSTANTS`. It won't
+    ///   collide with modules because the underscore is not allowed in module names.
+    /// - For each module, the functions will be stored in the `_FUNCTIONS` field, and the jobs in
+    ///   the `_JOBS` field.
+    /// - The extensions of the ASS elements are stored in the `_ASS` field as a sequence.
+    ///
+    /// For example, we may get:
+    ///
+    /// ```lua,no_run,skip
+    /// {
+    ///     "_CONSTANTS": { "a": 1, ... }
+    ///     "_ASS": { 1: { "line", "integer", 0 }, ... }
+    ///     "toto": { "_FUNCTIONS": { "b": ..., ... }, "_JOBS": { ... } }
+    ///     "foo": { }
+    /// }
+    /// ```
+    fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
+        if self.was_modified() {
+            return Err(LuaError::RuntimeError(format!(
+                "can't restore state for script '{}', files where modified after the bytecode was generated",
+                self.path.to_string_lossy()
+            )));
+        }
+        let (state, state_ass, state_constants) = (lua.create_table()?, lua.create_table()?, lua.create_table()?);
+
+        // Set constants and ASS extensions
+        for (name, value) in self.globals {
+            state_constants.raw_set(name, value)?;
+        }
+        for (name, ass_type, value) in self.data {
+            let element = lua.create_table()?;
+            element.raw_push(name)?;
+            element.raw_push(ass_type)?;
+            element.raw_push(value)?;
+            state_ass.raw_push(element)?;
+        }
+
+        // Import needed modules, i.e. internal ones and lua ones, the vvl ones are already dumped
+        // and we just need to load the bytecode.
+        for module in self.imports {
+            if !module.was_vivy_file() {
+                // Import the module because it couldn't be dumped (pure lua files and internal modules)
+                if module.was_modified() {
+                    return Err(LuaError::RuntimeError(format!(
+                        "can't restore state for module '{}', file was modified after bytecode generation",
+                        module.name
+                    )));
+                }
+                let name = module.name;
+                lua.load(chunk! { import($name) }).exec()?;
+            } else {
+                // Create the table, will be populated latter.
+                let table = lua.create_table()?;
+                table.raw_set("_FUNCTIONS", lua.create_table()?)?;
+                table.raw_set("_JOBS", lua.create_table()?)?;
+                state.raw_set(module.name, table)?;
+            }
+        }
+
+        // Load functions and jobs from dumped modules.
+        for (SymbolName { module, name }, symbol) in self.symbols {
+            log::debug!(target: "runtime", "load symbol '{module}.{name}': {symbol:?}");
+            let module_table: LuaTable = state.raw_get(module.as_str())?;
+            match symbol {
+                Symbol::Function { bytecode } => {
+                    module_table.raw_get::<_, LuaTable>("_FUNCTIONS")?.raw_set(
+                        name.as_str(),
+                        VivyFunc {
+                            module,
+                            name: name.clone(),
+                            doc: None,
+                            function: Arc::new(lua.create_registry_value(bytecode.to_lua(lua)?)?),
+                        },
+                    )?;
+                }
+
+                Symbol::Job { bytecode, input_type, output_type } => {
+                    module_table.raw_get::<_, LuaTable>("_JOBS")?.raw_set(
+                        name.as_str(),
+                        VivyJob {
+                            module,
+                            name: name.clone(),
+                            doc: None,
+                            input_type: input_type.into_inner(),
+                            return_type: output_type.into_inner(),
+                            function: Arc::new(lua.create_registry_value(bytecode.to_lua(lua)?)?),
+                        },
+                    )?;
+                }
+            }
+        }
+
+        state.raw_set("_ASS", state_ass)?;
+        state.raw_set("_CONSTANTS", state_constants)?;
+        Ok(LuaValue::Table(state))
+    }
+}
diff --git a/src/Rust/vvs_lua/src/libs/vivy/graph.rs b/src/Rust/vvs_lua/src/libs/vivy/graph.rs
new file mode 100644
index 00000000..9af8795d
--- /dev/null
+++ b/src/Rust/vvs_lua/src/libs/vivy/graph.rs
@@ -0,0 +1,171 @@
+use crate::{dsl, jobs::VivyCalledJob};
+use mlua::prelude::*;
+use scc::HashSet as SccHashSet;
+use std::{collections::HashSet as StdHashSet, sync::Arc};
+use thiserror::Error;
+use vvs_utils::either;
+
+/// Execution graph deduced from the user inputs.
+/// The returned item will be of the form:
+/// - destination variable name
+/// - input names
+/// - function registry key
+/// The iterator supports multi-threading in the sens that:
+/// - When queried it will returns a job that can be executed by knowing which jobs are left and
+///   which variables are already computed.
+/// - If no work is available but jobs still need to be executed, an error is returned:
+///   [VEGIterError::Again]
+#[derive(Debug)]
+pub struct VivyExecGraph {
+    nexts: SccHashSet<Arc<(String, VivyCalledJob)>>,
+    exits: SccHashSet<String>,
+    computed: SccHashSet<String>,
+}
+
+#[derive(Debug, Error)]
+pub enum VivyExecGraphIteratorError {
+    /// If no work is available but jobs still need to be executed.
+    #[error("check latter for available work")]
+    Again,
+
+    /// Work completed for the specified exit nodes, even if some jobs can be executed...
+    #[error(
+        "some jobs can be executed, but the minimal things to do has already been done for the output to be correct"
+    )]
+    Completed,
+
+    /// Like [Self::Completed], but we have no more work to do.
+    #[error("the iterator is empty and all the needed values have been computed for the result to be complete")]
+    Empty,
+
+    /// The work is incomplete, all needed variables where not produced.
+    #[error("the iterator is empty but not all needed variables have been computed for the output to be correct")]
+    Incomplete,
+}
+
+impl VivyExecGraph {
+    /// Get the next job to execute, or an error...
+    pub(crate) fn next(&self) -> Result<(String, VivyCalledJob), VivyExecGraphIteratorError> {
+        use VivyExecGraphIteratorError::*;
+        if self.nexts.is_empty() {
+            Err(either!(self.unfulfilled_exits() != 0 => Incomplete;  Empty))
+        } else if self.unfulfilled_exits() == 0 {
+            Err(Completed)
+        } else {
+            self.pop_next_edge()
+                .map(|ptr| Arc::try_unwrap(ptr).expect("we should only have one strong ref"))
+                .ok_or(Again)
+        }
+    }
+
+    /// Marks the computation of a variable as "completed". If the variable was really marked as
+    /// completed for the first time, then returns true, returns false otherwise.
+    pub fn completed(&self, var: impl ToString) -> bool {
+        self.computed
+            .insert(var.to_string())
+            .map_err(|var| log::error!(target: "runtime", "variable {var:?} was already completed"))
+            .is_ok()
+    }
+
+    /// Tells whever a variable was already completed (see [Self::completed]).
+    fn is_computed(&self, var: &str) -> bool {
+        self.computed.contains(var)
+    }
+
+    /// The number of the exit variables that are not completed (see [Self::completed]).
+    fn unfulfilled_exits(&self) -> usize {
+        let mut ret = 0;
+        self.exits.for_each(|var| ret += (!self.is_computed(var)) as usize);
+        ret
+    }
+
+    /// Get the next edges that can be executed.
+    fn pop_next_edge(&self) -> Option<Arc<(String, VivyCalledJob)>> {
+        let mut ret = None;
+        self.nexts.scan(|val| {
+            if ret.is_none() && val.1.arguments.iter().all(|var| self.is_computed(var)) {
+                ret = Some(val.clone());
+            }
+        });
+        log::debug!(target: "vvs", "next edge is {ret:?}, try to remove it if it was not already picked up by another thread");
+        ret.into_iter().flat_map(|ret| self.nexts.remove(&ret)).next()
+    }
+}
+
+impl<'lua, I> TryFrom<(&'lua Lua, I, LuaValue<'lua>)> for VivyExecGraph
+where
+    I: IntoIterator<Item = (usize, Option<LuaString<'lua>>, LuaValue<'lua>)>,
+{
+    type Error = LuaError;
+
+    fn try_from((lua, work, ret): (&'lua Lua, I, LuaValue<'lua>)) -> Result<Self, Self::Error> {
+        #[derive(Debug, Default)]
+        struct Builder {
+            nodes: StdHashSet<String>,
+            edges: Vec<(String, VivyCalledJob)>,
+            exits: Vec<String>,
+        }
+
+        impl Builder {
+            fn insert(&mut self, destination: impl ToString, function: VivyCalledJob) -> bool {
+                let dest = destination.to_string();
+                if function.arguments.iter().filter(|src| {
+                    either!(self.nodes.contains(src.as_str()) => false; {
+                        log::error!(target: "runtime", "source variable '{src}' is not present in the already assigned set");
+                        true
+                    })
+                }).count().ne(&0) {
+                    false
+                } else if !self.nodes.insert(dest.clone()) {
+                    log::error!(target: "runtime", "re-assignation of variable '{dest}' detected");
+                    false
+                } else {
+                    self.edges.push((dest, function));
+                    true
+                }
+            }
+        }
+
+        let mut graph = Builder { exits: dsl::into_string_vec(lua, ret)?, ..Default::default() };
+        for (idx, dest, src) in work {
+            let dest = dest.ok_or(LuaError::RuntimeError("".to_string()))?;
+            let dest = dest.to_string_lossy();
+            let src = match src {
+                LuaValue::UserData(src) if src.is::<VivyCalledJob>() => {
+                    src.take::<VivyCalledJob>().expect("very internal error")
+                }
+                _ => return Err(LuaError::RuntimeError("".to_string())),
+            };
+            if !graph.insert(&dest, src) {
+                return Err(LuaError::RuntimeError(format!(
+                    "invalid instruction n°{idx} in main block, destination variable was: {dest:?}"
+                )));
+            }
+        }
+        let invalids = graph.exits.iter().filter(|exit| {
+            either!(graph.nodes.contains(exit.as_str()) => false; {
+                log::error!(target: "runtime", "invalid exit node '{exit}', variable was never assigned");
+                true
+            })
+        });
+        if invalids.count().ne(&0) {
+            Err(LuaError::RuntimeError(format!(
+                "invalid exit nodes for execution graph: {graph:#?}"
+            )))
+        } else {
+            let (exits, nexts, computed) = (
+                SccHashSet::with_capacity(graph.exits.len()),
+                SccHashSet::with_capacity(graph.edges.len()),
+                SccHashSet::with_capacity(graph.edges.len()),
+            );
+            for var in graph.exits {
+                exits.insert(var).expect("very internal error");
+            }
+            for var in graph.edges {
+                nexts.insert(Arc::new(var)).expect("very internal error");
+            }
+            computed.insert("INIT".to_string()).expect("very internal error");
+            Ok(Self { computed, nexts, exits })
+        }
+    }
+}
diff --git a/src/Rust/vvs_lua/src/libs/vivy/runtime.rs b/src/Rust/vvs_lua/src/libs/vivy/runtime.rs
new file mode 100644
index 00000000..dc81c86f
--- /dev/null
+++ b/src/Rust/vvs_lua/src/libs/vivy/runtime.rs
@@ -0,0 +1,1061 @@
+use crate::{
+    data::{VivyDataRegister, VivyDataRegisterPtr},
+    dsl,
+    func::{VivyFuncRegister, VivyFuncRegisterPtr},
+    jobs::{VivyCalledJob, VivyJobRegister, VivyJobRegisterPtr},
+    libs::vivy::{
+        byte_code::{FunctionByteCode, Module, ScriptByteCode, Symbol, SymbolName},
+        graph::{VivyExecGraph, VivyExecGraphIteratorError},
+    },
+    lua_wrapper::{
+        LuaAssAuxValue, LuaAssContainerPtr, LuaAssLinePtr, LuaAssLines, LuaAssSyllabePtr, LuaAssSyllabes, LuaAssType,
+    },
+    options::{VivyOptionRegister, VivyOptionRegisterPtr},
+    TomlOptions,
+};
+use mlua::{chunk, prelude::*};
+use scc::HashMap as SccHashMap;
+use std::{
+    cell::RefCell,
+    collections::{HashMap, HashSet},
+    path::PathBuf,
+    sync::Arc,
+    time::SystemTime,
+};
+use vvs_ass::{ASSAuxTable, ASSLine, ASSSyllabe, ASSType, ASSTYPE_VALUES};
+
+/// We have different ways of passing the ASS file we want to handle, either it's not already
+/// parsed and we have a file path, or it was already parsed/constructed and we have its in-memory
+/// representation.
+#[derive(Debug, Clone)]
+pub(crate) enum ASSFileOrInstance {
+    /// The path to the ASS file, parsing it may fail and errors should be handled at the vivy
+    /// runtime creation.
+    File(PathBuf),
+
+    /// The ASS file was already parsed. It is a valide ASS representation.
+    Instance(LuaAssContainerPtr),
+}
+
+/// The mode of the runtime to create.
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum RuntimeMode {
+    /// The full runtime, with DSL keywords, used to parse modules' definitions.
+    #[default]
+    Full,
+
+    /// The minimal runtime, used for executing what was parsed and dumped.
+    Compute,
+
+    /// Like the full runtime, but for REPL (some restrictions are lifted like you can declare
+    /// global variables, etc).
+    Interactive,
+}
+
+/// What to use to create the runtime.
+#[derive(Debug)]
+pub(crate) enum RuntimeCreationData {
+    /// Init a brand new runtime with only a toml file.
+    TomlFile(TomlOptions),
+
+    /// Init from a dumped state.
+    ByteCode(ScriptByteCode),
+}
+
+#[derive(Default, Debug)]
+pub(crate) struct RuntimeOptions {
+    /// The ASS file to use.
+    pub ass_file: Option<ASSFileOrInstance>,
+
+    /// What to use to init the runtime.
+    pub creation_data: Option<RuntimeCreationData>,
+
+    /// The include path specified by the user, will be appened after the $PWD and $BASEDIR.
+    pub include_path: Vec<PathBuf>,
+
+    /// The mode of the runtime to create, depends on its usage.
+    pub mode: RuntimeMode,
+}
+
+#[derive(Default, Debug)]
+pub struct Vivy {
+    options: Option<VivyOptionRegisterPtr>,
+    data: Option<VivyDataRegisterPtr>,
+    jobs: Option<VivyJobRegisterPtr>,
+    func: Option<VivyFuncRegisterPtr>,
+    path: Vec<String>,
+
+    variables: SccHashMap<String, Arc<LuaRegistryKey>>,
+    upvalues_getter: Option<LuaRegistryKey>,
+    func_bc_dump: Option<LuaRegistryKey>,
+
+    ass_file: Option<ASSFileOrInstance>,
+
+    should_print_infos: bool,
+    main_once: bool,
+
+    // We refcell those because we want to avoid the reborow mut of the import process.
+    loaded: RefCell<HashMap<String, Module>>,
+    loading: RefCell<HashSet<String>>,
+    current_module: RefCell<Option<String>>,
+
+    script_path: Option<PathBuf>,
+    script_path_mtime: Option<SystemTime>,
+}
+
+macro_rules! if_let_else_chain {
+    (@default: $default_expr: expr) => {{ $default_expr }};
+
+    ($var: ident <- $expr: expr => $var_expr: expr;
+     @default: $default_expr: expr
+    ) => {
+        if let Ok($var) = $expr { $var_expr } else { $default_expr }
+    };
+
+    ($var_first: ident <- $expr_first: expr => $var_expr_first: expr;
+     $($var: ident <- $expr: expr => $var_expr: expr);+;
+     @default: $default_expr: expr
+    ) => {
+        if let Ok($var_first) = $expr_first {
+            $var_expr_first
+        } else { if_let_else_chain! {
+            $($var <- $expr => $var_expr);+;
+            @default: $default_expr
+        } }
+    };
+}
+
+/// See [Vivy::execute_job] for an example on how to use this macro.
+macro_rules! match_borrow {
+    ($expr: expr =>
+     $(; $var: ident: $ty: ident => $var_expr: expr)+
+     $(; @default => $default_expr: expr)?
+    ) => {{
+        if_let_else_chain! {
+            $($var <- $expr.borrow::<$ty>() => $var_expr);+;
+            $(@default: $default_expr)?
+        }
+    }};
+}
+
+impl Vivy {
+    /// Create a new instance of the vivy module, with a specified include path.
+    pub fn new_with_path(path: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
+        Self {
+            path: path.into_iter().map(|str| str.as_ref().to_string()).collect(),
+            ..Default::default()
+        }
+    }
+
+    /// Setup the Vivy runtime, creating the Lua state. You can setup the complete runtime, used to
+    /// parse declared jobs and all this stuff with the DSL-like keywords, or the minimal one used
+    /// to handle the computations.
+    pub(crate) fn setup_runtime(opts: RuntimeOptions) -> LuaResult<Lua> {
+        // The common things to create
+        let lua = unsafe {
+            // We use unsafe because we need to load the debug package. This also allows to load C
+            // things and dylib things, but because we nil the requires function it should be fine.
+            Lua::unsafe_new_with(
+                LuaStdLib::STRING | LuaStdLib::MATH | LuaStdLib::TABLE | LuaStdLib::PACKAGE | LuaStdLib::DEBUG,
+                LuaOptions::new().catch_rust_panics(true),
+            )
+        };
+        let (include_path, version, requires, warning, info, get_type_name) = (
+            opts.include_path
+                .into_iter()
+                .map(|path| path.to_string_lossy().to_string())
+                .collect::<Vec<_>>(),
+            env!("CARGO_PKG_VERSION"),
+            lua.create_function(crate::libs::import)?,
+            lua.create_function(|_, msg: String| {
+                log::warn!(target: "vvs", "{msg}");
+                Ok(())
+            })?,
+            lua.create_function(|_, msg: String| {
+                log::info!(target: "vvs", "{msg}");
+                Ok(())
+            })?,
+            lua.create_function(|lua, value: LuaValue| match value {
+                LuaValue::UserData(value) => {
+                    let Ok(mt) = value.get_metatable() else { return lua.create_string("userdata") };
+                    let Ok(name) = mt.get::<_, LuaFunction>("_name") else { return lua.create_string("userdata:_unamed") };
+                    lua.load(chunk! { "userdata:" .. $name() }).eval()
+                }
+                value => lua.create_string(value.type_name()),
+            })?,
+        );
+
+        // Remenber the mode flags for latter.
+        let (interactive_mode, minimal_mode) = match opts.mode {
+            RuntimeMode::Full => (false, false),
+            RuntimeMode::Compute => (false, true),
+            RuntimeMode::Interactive => (true, false),
+        };
+
+        // Mode specific constructs
+        let (options, data, jobs, func) = match opts.mode {
+            // In compute mode, we only need the dumps, ignoring almost all the things.
+            RuntimeMode::Compute => (LuaNil, LuaNil, LuaNil, LuaNil),
+
+            // For the full or interactive runtime we need the DSL things, with the registers.
+            RuntimeMode::Full | RuntimeMode::Interactive => {
+                let options = match opts.creation_data {
+                    Some(RuntimeCreationData::TomlFile(ref options)) => options.clone(),
+                    _ => Default::default(),
+                };
+                (
+                    VivyOptionRegister::new(options).to_lua(&lua)?,
+                    VivyDataRegister::new().to_lua(&lua)?,
+                    VivyJobRegister::new().to_lua(&lua)?,
+                    VivyFuncRegister::new().to_lua(&lua)?,
+                )
+            }
+        };
+
+        // Handle the ASS file.
+        let ass_value = match opts.ass_file {
+            Some(ASSFileOrInstance::File(file)) => {
+                LuaValue::String(lua.create_string(file.to_str().ok_or_else(|| {
+                    LuaError::RuntimeError(format!(
+                        "the path for the ASS file is not a valid UTF8 string: {}",
+                        file.to_string_lossy()
+                    ))
+                })?)?)
+            }
+            Some(ASSFileOrInstance::Instance(instance)) => instance.to_lua(&lua)?,
+            None => LuaNil,
+        };
+
+        lua.load(chunk! {
+            local _debug   = debug
+            local ReadOnly = {
+                _VERSION = "Vivy " .. $version,
+                warning  = $warning,
+                info     = $info,
+                type     = $get_type_name,
+            }
+
+            local function _job_input_type (func)
+                local nparams = _debug.getinfo(func).nparams
+                if nparams ~= 1 then
+                    error("a job should only have one argument, got " .. nparams, 2)
+                end
+                return _debug.getlocal(func, 1)
+            end
+
+            local function _get_upvalues(func)
+                local ret, upval, getupval = {}, 1, _debug.getupvalue
+                while getupval(func, upval) do
+                    ret[upval] = getupval(func, upval)
+                    upval      = upval + 1
+                end
+                return ret
+            end
+
+            local function _set_upvalue(func, idx, value)
+                _debug.setupvalue(func, idx, value)
+            end
+
+            // Import Vivy
+            $requires("vivy", false)
+            vivy:___set_ass_file($ass_value)
+            vivy:___set_options($options)
+            vivy:___set_data($data)
+            vivy:___set_jobs($jobs)
+            vivy:___set_func($func)
+            vivy:___set_upvalues_callback(_get_upvalues, _set_upvalue)
+            vivy:___set_func_bc_dump_callback(string.dump, loadstring)
+            for _, folder in ipairs($include_path) do vivy:___append_to_path(folder) end
+            ReadOnly.vivy = vivy
+
+            for _, l in ipairs { "package", "debug", "coroutine", "string", "_G" } do
+                package.loaded[l] = nil
+            end
+            for name, _ in pairs(package.loaded) do
+                ReadOnly.vivy:___mark_loaded(name)
+            end
+
+            // DSL keywords
+            function ReadOnly.import (name)
+                if type(name) ~= "string" then
+                    error("you must import a module by its name, got " .. type(name) .. " and not a string", 2)
+                end
+                $requires(name, ReadOnly)
+            end
+
+            if $minimal_mode ~= true then
+                function ReadOnly.main (table)
+                    if type(table) ~= "table" then
+                        error("you must specify the main workflow with a table", 2)
+                    end
+                    vivy:___main(table)
+                end
+
+                function ReadOnly.func (name)
+                    if type(name) ~= "string" then
+                        error("you must specify the exported function name with a string", 2)
+                    end
+                    return function (table)
+                        if type(table) ~= "table" then
+                            error("you must specify the function description with a table", 2)
+                        end
+                        // error("not implemented")
+                    end
+                end
+
+                function ReadOnly.job (name)
+                    if type(name) ~= "string" then
+                        error("you must specify the job name with a string", 2)
+                    end
+                    $jobs:will_register(name)
+                    return function (table)
+                        if type(table) ~= "table" then
+                            error("you must specify the job description with a table", 2)
+                        end
+                        table._getter = _job_input_type
+                        $jobs:register(name, table)
+                    end
+                end
+
+                function ReadOnly.data (name)
+                    if type(name) ~= "string" then
+                        error("you must specify the datum name with a string", 2)
+                    end
+                    $data:will_register(name)
+                    return function (table)
+                        if type(table) ~= "table" then
+                            error("you must specify the data description with a table", 2)
+                        end
+                        $data:register(name, table)
+                    end
+                end
+
+                function ReadOnly.set (name)
+                    if type(name) ~= "string" then
+                        error("you must specify the option name with a string", 2)
+                    end
+                    $options:will_set(name)
+                    return function (table)
+                        if type(table) ~= "table" then
+                            error("you must specify the option specification description with a table", 2)
+                        end
+                        $options:set(name, table)
+                    end
+                end
+
+                function ReadOnly.option (name)
+                    if type(name) ~= "string" then
+                        error("you must specify the option name with a string", 2)
+                    end
+                    $options:will_register(name)
+                    return function (table)
+                        if type(table) ~= "table" then
+                            error("you must specify the option description with a table", 2)
+                        end
+                        $options:register(name, table)
+                    end
+                end
+            end
+
+            // Protect the metatable and control what is in the global table and force to use the
+            // proxy table...
+            setmetatable(_G, {
+                __metatable = "don't touch to the global table",
+                __index     = ReadOnly,
+                __newindex  = function(_, n, v)
+                    if rawget(ReadOnly, n) ~= nil then
+                        error("keyword or read only variable '" .. n .. "'", 2)
+                    elseif type(n) ~= "string" then
+                        error("global variables names must be strings", 2)
+                    elseif $interactive_mode then
+                        rawset(_G, n, v)
+                    else
+                        error("you can't create global variables", 2)
+                    end
+                end,
+            })
+
+            for _, n in ipairs {
+                "debug", "vivy", "coroutine", "package", "string",
+                "module", "type", "_VERSION", "newproxy",
+                "require", "pcall", "xpcall", "loadfile", "load", "loadstring", "dofile",
+            } do
+                rawset(_G, n, nil)
+            end
+
+            for _, n in ipairs {
+                "table", "math",                                      // Modules
+                "next", "pairs", "ipairs",                            // Iteration stuff
+                "tonumber", "tostring", "print",                      // Printing stuff
+                "assert", "error",                                    // Error handling
+                "unpack", "select",                                   // Functions for tables
+                "gcinfo", "collectgarbage",                           // Should we expose the garbase collector?
+                "getmetatable", "setmetatable", "getfenv", "setfenv", // Handle metatables and other cryptic stuff
+                "rawget", "rawequal", "rawlen", "rawset"              // rawset should be last...
+            } do
+                rawset(ReadOnly, n, _G[n])
+                rawset(_G, n, nil)
+            end
+        })
+        .set_name("vivy")?
+        .exec()?;
+
+        match (opts.mode, opts.creation_data) {
+            (RuntimeMode::Compute, Some(RuntimeCreationData::TomlFile(_))) => {
+                return Err(LuaError::RuntimeError(
+                    "can't use a toml file to init the runtime in compute mode".to_string(),
+                ))
+            }
+            (RuntimeMode::Compute, Some(RuntimeCreationData::ByteCode(bc))) => {
+                bc.to_lua(&lua)?;
+            }
+            (RuntimeMode::Interactive | RuntimeMode::Full, Some(RuntimeCreationData::ByteCode(_))) => todo!(),
+            _ => {}
+        }
+        log::debug!(target: "runtime", "finished runtime setup in mode {:?}", opts.mode);
+
+        Ok(lua)
+    }
+
+    /// Create a new default ASS element of the asked type.
+    #[allow(dead_code)]
+    pub fn new_ass_element<'lua>(&self, lua: &'lua Lua, ty: ASSType) -> LuaResult<LuaValue<'lua>> {
+        macro_rules! new {
+            ($elem: ident) => {
+                paste::paste! {{
+                    let mut elem = [< ASS $elem >] ::default();
+                    elem.aux = self.new_ass_aux_table(ASSType::Line);
+                    [< LuaAss $elem Ptr >] (elem.into()).to_lua(lua)
+                }}
+            };
+        }
+        match ty {
+            ASSType::Line => new!(Line),
+            ASSType::Syllabe => new!(Syllabe),
+            ASSType::Lines => LuaAssLines::default().to_lua(lua),
+            ASSType::Syllabes => LuaAssSyllabes::default().to_lua(lua),
+        }
+    }
+
+    /// Create a new default ASS element aux table of the asked type. The table is filled with the
+    /// default options for the said element.
+    pub fn new_ass_aux_table(&self, ty: ASSType) -> ASSAuxTable {
+        self.data
+            .as_ref()
+            .map(|data| data.try_read().unwrap().get_table(ty))
+            .unwrap_or_default()
+    }
+
+    /// Get the associated variables, get by names.
+    fn get_vars<'lua>(&self, lua: &'lua Lua, args: &[impl AsRef<str>]) -> LuaResult<Vec<LuaValue<'lua>>> {
+        let (vars, errs): (Vec<_>, Vec<_>) = args
+            .iter()
+            .map(|arg| match self.variables.read(arg.as_ref(), |_, v| v.clone()) {
+                Some(key) => match lua.registry_value::<LuaValue>(&*key)? {
+                    var @ LuaValue::UserData(_) => {
+                        log::debug!(target: "runtime", "{} -> {key:?}", arg.as_ref());
+                        Ok(var)
+                    }
+                    var => Err(LuaError::RuntimeError(format!(
+                        "invalid type '{}' for job argument '{}'",
+                        var.type_name(),
+                        arg.as_ref()
+                    ))),
+                },
+                None => Err(LuaError::RuntimeError(format!(
+                    "failed to find variable '{}' in execution state",
+                    arg.as_ref()
+                ))),
+            })
+            .partition(Result::is_ok);
+        if let Some(Err(err)) = errs.into_iter().next() {
+            return Err(err);
+        }
+        Ok(vars.into_iter().map(Result::unwrap).collect())
+    }
+
+    /// Pack the arguments for a job, check for correct type, coerce if needed, etc.
+    fn pack_job_arguments<'lua>(
+        &self,
+        lua: &'lua Lua,
+        input: ASSType,
+        input_args: Vec<String>,
+    ) -> LuaResult<Vec<LuaValue<'lua>>> {
+        let mut args: Vec<LuaValue> = Default::default();
+        for var in self.get_vars(lua, &input_args)? {
+            let LuaValue::UserData(var) = var else {
+                panic!("we should have a user data type here, got {}", var.type_name())
+            };
+            match input {
+                ASSType::Lines | ASSType::Line => match_borrow! { var =>
+                    ; _var: LuaAssLinePtr => { args.push(LuaValue::UserData(var.clone())); }
+                    ;  var: LuaAssLines   => {
+                        for var in &var.as_inner().0 {
+                            args.push(LuaAssLinePtr::from(var.clone()).to_lua(lua)?);
+                        }
+                    }
+
+                    ; var: LuaAssContainerPtr => {
+                        for var in var.0.0.try_read().unwrap().lines.0.iter() {
+                            args.push(LuaAssLinePtr::from(var.clone()).to_lua(lua)?);
+                        }
+                    }
+
+                    ; @default => {
+                        let var = var.clone();
+                        let ty = lua.load(chunk!(type($var))).eval::<LuaString>()?;
+                        let ty = ty.to_str()?;
+                        return Err(LuaError::RuntimeError(format!("expected a line or lines, got {ty}")))
+                    }
+                },
+
+                ASSType::Syllabes | ASSType::Syllabe => match_borrow! { var =>
+                    ; _var: LuaAssSyllabePtr => { args.push(LuaValue::UserData(var.clone())); }
+                    ; var: LuaAssSyllabes    => {
+                        for var in &var.as_inner().0 {
+                            args.push(LuaAssSyllabePtr::from(var.clone()).to_lua(lua)?);
+                        }
+                    }
+
+                    ; var: LuaAssLinePtr => {
+                        for var in &var.as_inner().0.try_read().unwrap().content {
+                            args.push(LuaAssSyllabePtr(var.clone()).to_lua(lua)?);
+                        }
+                    }
+                    ; var: LuaAssLines => for line in &var.as_inner().0 {
+                        for var in &line.0.try_read().unwrap().content {
+                            args.push(LuaAssSyllabePtr(var.clone()).to_lua(lua)?);
+                        }
+                    }
+
+                    ; var: LuaAssContainerPtr => for line in var.as_inner().0.try_read().unwrap().lines.0.iter() {
+                        for var in &line.0.try_read().unwrap().content {
+                            args.push(LuaAssSyllabePtr(var.clone()).to_lua(lua)?);
+                        }
+                    }
+
+                    ; @default => {
+                        let var = var.clone();
+                        let ty = lua.load(chunk!(type($var))).eval::<LuaString>()?;
+                        let ty = ty.to_str()?;
+                        return Err(LuaError::RuntimeError(format!("expected a line or lines, got {ty}")))
+                    }
+                },
+            };
+        }
+        Ok(args)
+    }
+
+    /// Unpack the return values from a job to form the correct list by taking into account what
+    /// should be returned by the job.
+    fn unpack_job_returns<'lua>(
+        &self,
+        lua: &'lua Lua,
+        name: &str,
+        returns: ASSType,
+        output: impl Iterator<Item = LuaValue<'lua>>,
+    ) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
+        fn unwrap(var: LuaValue) -> LuaAnyUserData {
+            match var {
+                LuaValue::UserData(var) => var,
+                _ => unreachable!(),
+            }
+        }
+
+        fn handle_user_data<'lua>(
+            lua: &'lua Lua,
+            returns: ASSType,
+            var: LuaAnyUserData<'lua>,
+        ) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
+            match returns {
+                ASSType::Lines | ASSType::Line => match_borrow! { var =>
+                    ; var: LuaAssLinePtr => Ok(vec![unwrap(var.clone().to_lua(lua)?)])
+                    ; var: LuaAssLines   => Ok(var.as_inner().0.iter().map(|line|
+                        unwrap(LuaAssLinePtr::from(line.clone()).to_lua(lua).unwrap())
+                    ).collect())
+                    ; @default => Err(LuaError::RuntimeError(
+                        "expected a line or lines".to_string()
+                    ))
+                },
+                ASSType::Syllabes | ASSType::Syllabe => match_borrow! { var =>
+                    ; var: LuaAssSyllabePtr => Ok(vec![unwrap(var.clone().to_lua(lua)?)])
+                    ; var: LuaAssSyllabes   => Ok(var.as_inner().0.iter().map(|line|
+                        unwrap(LuaAssSyllabePtr::from(line.clone()).to_lua(lua).unwrap())
+                    ).collect())
+                    ; @default => Err(LuaError::RuntimeError(
+                        "expected a syllabe or syllabes".to_string()
+                    ))
+                },
+            }
+        }
+
+        let (output, errs): (Vec<_>, Vec<_>) = output
+            .map(|out: LuaValue<'lua>| match out {
+                LuaValue::UserData(out) => handle_user_data(lua, returns, out),
+                LuaValue::Table(outs) => {
+                    let (outs, errs): (Vec<_>, Vec<_>) = outs
+                        .sequence_values()
+                        .map(|item| handle_user_data(lua, returns, item?))
+                        .partition(Result::is_ok);
+                    if let Some(Err(err)) = errs.into_iter().next() {
+                        return Err(err);
+                    }
+                    Ok(outs.into_iter().flat_map(Result::unwrap).collect())
+                }
+                _ => Err(LuaError::RuntimeError(format!(
+                    "invalid return type for job '{name}', got a value of type: {}",
+                    out.type_name()
+                ))),
+            })
+            .partition(Result::is_ok);
+        if let Some(Err(err)) = errs.into_iter().next() {
+            return Err(err);
+        }
+        let (output, errs): (Vec<_>, Vec<_>) = output.into_iter().partition(Result::is_ok);
+        if let Some(Err(err)) = errs.into_iter().next() {
+            return Err(err);
+        }
+        Ok(output.into_iter().flat_map(Result::unwrap).collect())
+    }
+
+    /// Execute a job with the named arguments. The types of the arguments will be checked and it
+    /// will be up to this function to create the apply loop if needed. The returned values will
+    /// also be flattened if needed.
+    fn execute_job<'lua>(&self, lua: &'lua Lua, job: VivyCalledJob) -> LuaResult<Vec<LuaAnyUserData<'lua>>> {
+        let VivyCalledJob { module: _, name, function, arguments, input_type, return_type } = job;
+        let function: LuaFunction = lua.registry_value(&function)?;
+        let (output, errs): (Vec<_>, Vec<_>) = self
+            .pack_job_arguments(lua, input_type, arguments)?
+            .into_iter()
+            .map(|arg| function.call::<LuaValue, LuaValue>(arg))
+            .partition(Result::is_ok);
+        if let Some(Err(err)) = errs.into_iter().next() {
+            return Err(err);
+        }
+        self.unpack_job_returns(lua, &name, return_type, output.into_iter().map(Result::unwrap))
+    }
+
+    /// Register a Vivy execution variable.
+    fn register_variable<'lua>(&self, lua: &'lua Lua, name: impl ToString, value: impl ToLua<'lua>) -> LuaResult<()> {
+        let (key, name) = (
+            Arc::new(lua.create_registry_value(value.to_lua(lua)?)?),
+            name.to_string(),
+        );
+        log::debug!(target: "runtime", "create variable {name:?} as {key:?}");
+        self.variables
+            .insert(name, key)
+            .map_err(|(var, _)| LuaError::RuntimeError(format!("variable {var:?} already exists...")))
+    }
+
+    /// Export all registered options into Lua's global table.
+    fn set_options(&self, lua: &Lua) -> LuaResult<()> {
+        self.options
+            .as_ref()
+            .map(|options| {
+                let options = options
+                    .try_read()
+                    .map_err(|err| LuaError::RuntimeError(format!("failed to lock option register: {err}")))?;
+                for name in options.iter_registered() {
+                    let opt = options.resolve(name).ok_or_else(|| {
+                        LuaError::RuntimeError(format!(
+                            "the option {name:?} should have been defined in the toml config but was not"
+                        ))
+                    })?;
+                    log::debug!(target: "runtime", "set option {name:?} as {opt:?}");
+                    lua.globals().raw_set(name.as_str(), opt)?;
+                }
+                Ok(())
+            })
+            .unwrap_or_else(|| Err(LuaError::RuntimeError("no option register found...".to_string())))
+    }
+
+    /// Create the byte code structure associated to the user's script.
+    fn create_byte_code(&self, lua: &Lua) -> LuaResult<ScriptByteCode> {
+        let getters = (
+            self.upvalues_getter.as_ref().unwrap(),
+            self.func_bc_dump.as_ref().unwrap(),
+        );
+        let Some(ref path) = self.script_path else {
+            return Err(LuaError::RuntimeError(match &*self.current_module.borrow() {
+                Some(ref module) => format!("no script file found for module '{module}'"),
+                None => "no main script file found".to_string(),
+            }))
+        };
+        let Some(ref jobs) = self.jobs    else { return Err(LuaError::RuntimeError("no jobs registry found in the runtime".to_string())) };
+        let Some(ref func) = self.func    else { return Err(LuaError::RuntimeError("no functions registry found in the runtime".to_string())) };
+        let Some(ref data) = self.data    else { return Err(LuaError::RuntimeError("no data registry found in the runtime".to_string())) };
+        let Some(ref opts) = self.options else { return Err(LuaError::RuntimeError("no option registry found in the runtime".to_string())) };
+
+        fn log_error<T>(res: LuaResult<T>) -> Option<T> {
+            res.map_err(|err| log::error!(target: "runtime", "{err}")).ok()
+        }
+
+        let data = data.try_read().unwrap();
+        let data = ASSTYPE_VALUES.iter().copied().flat_map(|ass_type| {
+            data.get_table(ass_type)
+                .into_iter()
+                .map(move |(name, default)| (name, LuaAssType(ass_type), LuaAssAuxValue(default)))
+        });
+
+        let opts = opts.try_read().unwrap();
+        let globals = opts.iter_registered().flat_map(|name| {
+            opts.resolve(name)
+                .into_iter()
+                .filter_map(|value| Some((name.clone(), LuaAssAuxValue(value.try_into().ok()?))))
+        });
+
+        let (jobs, func) = (jobs.try_read().unwrap(), func.try_read().unwrap());
+        let jobs = jobs.iter_declared().filter_map(|job_ptr| {
+            let job = job_ptr.try_read().unwrap();
+            let ident = SymbolName { module: job.module.clone(), name: job.name.clone() };
+            let function = log_error(lua.registry_value(&job.function))?;
+            let job = Symbol::Job {
+                bytecode: log_error(FunctionByteCode::try_new(getters, lua, function))?,
+                input_type: LuaAssType(job.input_type),
+                output_type: LuaAssType(job.return_type),
+            };
+            Some((ident, job))
+        });
+        let func = func.iter_declared().filter_map(|func_ptr| {
+            let func = func_ptr.try_read().unwrap();
+            let ident = SymbolName { module: func.module.clone(), name: func.name.clone() };
+            let function = log_error(lua.registry_value(&func.function))?;
+            let function = Symbol::Function {
+                bytecode: log_error(FunctionByteCode::try_new(getters, lua, function))?,
+            };
+            Some((ident, function))
+        });
+
+        Ok(ScriptByteCode {
+            path: path.clone(),
+            data: data.collect(),
+            mtime: self.script_path_mtime,
+            imports: self.loaded.borrow().values().cloned().collect(),
+            globals: globals.collect(),
+            symbols: HashMap::from_iter(jobs.chain(func)),
+        })
+    }
+
+    /// Execute a graph. This is the what handles the work to do that was declared by the user.
+    /// From here we need an ASS file (or the JSON file, or any sub-title able file).
+    fn execute_graph(&self, lua: &Lua, graph: VivyExecGraph) -> LuaResult<()> {
+        let ass = match self.ass_file.as_ref() {
+            None => {
+                return Err(LuaError::RuntimeError(
+                    "no ASS file was specified, can't execute the user script".to_string(),
+                ))
+            }
+            Some(ASSFileOrInstance::Instance(instance)) => instance.clone(),
+            Some(ASSFileOrInstance::File(file)) => {
+                if !file.exists() || !file.is_file() {
+                    return Err(LuaError::RuntimeError(format!(
+                        "specified ASS file doesn't exist: {}",
+                        file.to_string_lossy()
+                    )));
+                }
+                let file_name = file.to_string_lossy();
+                LuaAssContainerPtr::from(
+                    vvs_ass::ass_container_from_file(file)
+                        .map_err(|err| LuaError::RuntimeError(format!("invalid ass file '{file_name}': {err}")))?,
+                )
+            }
+        };
+        self.register_variable(lua, "INIT", ass)?;
+
+        'working: loop {
+            use VivyExecGraphIteratorError::*;
+            match graph.next() {
+                Ok((dest, job)) => {
+                    log::info!(target: "runtime", "{dest} := {job:?}");
+                    self.register_variable(lua, &dest, self.execute_job(lua, job)?)?;
+                    graph.completed(dest);
+                }
+                Err(Empty | Completed) => break 'working,
+                Err(Again) => {
+                    log::debug!(target: "runtime", "no work to do for now...");
+                    continue 'working;
+                }
+                Err(err) => return Err(LuaError::RuntimeError(format!("script failed: {err}"))),
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Format the loaded modules, internal modules are preceded with the '@' character.
+    fn format_loaded(&self) -> Vec<String> {
+        self.loaded
+            .borrow()
+            .iter()
+            .map(|(name, module)| {
+                if module.is_internal() {
+                    format!("@{name}")
+                } else if module.was_lua_file() {
+                    format!("lua:{name}")
+                } else if module.was_vivy_file() {
+                    format!("vvl:{name}")
+                } else {
+                    unreachable!()
+                }
+            })
+            .collect()
+    }
+}
+
+impl LuaUserData for Vivy {
+    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+        methods.add_meta_function("_name", |lua, ()| lua.create_string("Vivy"));
+
+        macro_rules! add_setter {
+            ($n: ident: $p: ident;
+             $($name: ident: $ptr: ident);+$(;)?
+            ) => {
+                add_setter! { $n: $p }
+                add_setter! { $($name: $ptr);+ }
+            };
+
+            ($name: ident: $ptr: ident $(;)?) => {
+                methods.add_method_mut(concat!("___set_", stringify!($name)), |_, this, $name: Option<$ptr>| {
+                    this.$name = $name;
+                    Ok(())
+                });
+            };
+
+            ($name: literal ($($arg: ident: $ty: ty),+) $lua: ident, $this: ident => $expr: expr) => {
+                methods.add_method_mut($name, |$lua, $this, ($($arg),+,): ($($ty),+,)| {
+                    $expr;
+                    Ok(())
+                });
+            };
+        }
+
+        macro_rules! add_getter {
+            ($n: literal: $l: ident , $t: ident => $e: expr;
+             $($name: literal: $lua: ident , $this: ident => $expr: expr);+$(;)?
+            ) => {
+                add_getter! { $n: $l, $t => $e }
+                add_getter! { $($name: $lua, $this => $expr);+ }
+            };
+
+            ($name: literal: $lua: ident, $this: ident => $expr: expr $(;)?) => {
+                methods.add_method($name, |$lua, $this, ()| Ok($expr));
+            };
+        }
+
+        add_setter! {
+            options: VivyOptionRegisterPtr;
+            data: VivyDataRegisterPtr;
+            jobs: VivyJobRegisterPtr;
+            func: VivyFuncRegisterPtr;
+        }
+
+        add_setter! { "___set_ass_file" (value: LuaValue) _lua, this => {
+            match value {
+                LuaValue::String(file) => {
+                    let file = PathBuf::from(file.to_string_lossy().as_ref());
+                    this.ass_file = Some(ASSFileOrInstance::File(file));
+                }
+                LuaValue::UserData(ud) => {
+                    if let Ok(instance) = ud.take::<LuaAssContainerPtr>() {
+                        this.ass_file = Some(ASSFileOrInstance::Instance(instance));
+                    }
+                }
+                _ => {}
+            }
+        }}
+        add_setter! { "___set_user_script" (file: LuaString) _lua, this => {
+            let file = PathBuf::from(file.to_string_lossy().as_ref());
+            this.script_path_mtime = std::fs::metadata(&file).map(|mt| mt.modified().ok()).unwrap_or_default();
+            this.script_path = Some(file);
+        }}
+        add_setter! { "___prepend_to_path" (pattern: String) _lua, this => this.path.insert(0, pattern) }
+        add_setter! { "___append_to_path"  (pattern: String) _lua, this => this.path.push(pattern) }
+        add_setter! { "___mark_loaded"     (modname: String) _lua, this => {
+            this.loading.borrow_mut().remove(&modname);
+            this.loaded.borrow_mut().entry(modname).or_insert_with_key(|modname| Module::from_internal(modname));
+        }}
+        add_setter! { "___set_upvalues_callback" (get: LuaFunction, set: LuaFunction) lua, this => {
+            this.upvalues_getter = Some(lua.create_registry_value(get)?);
+            lua.set_named_registry_value("_set_upvalue", set)?;
+        }}
+        add_setter! { "___set_func_bc_dump_callback" (dump: LuaFunction, load: LuaFunction) lua, this => {
+            this.func_bc_dump = Some(lua.create_registry_value(dump)?);
+            lua.set_named_registry_value("_loadstring", load)?;
+        }}
+        add_setter! { "___set_current_module" (module: LuaValue) _lua, this =>
+            this.current_module.replace(match module {
+                LuaValue::String(module) => {
+                    let module = module.to_string_lossy().to_string();
+                    log::debug!(target: "runtime", "set the current module to '{module}'");
+                    Some(module)
+                }
+                value => {
+                    log::debug!(target: "runtime", "can't set the current module name with a value of type: {}", value.type_name());
+                    None
+                }
+            })
+        }
+
+        add_getter! {
+            "___get_loaded":  _lua, this => this.format_loaded().into_iter().collect::<Vec<_>>();
+            "include_path":   _lua, this => this.path.clone();
+            "loaded_modules": lua, this => {
+                let table = lua.create_table()?;
+                for (module, desc) in this.loaded.borrow().iter().map(|(name, module)| {
+                    if   module.is_internal()      { (name, "internal"    ) }
+                    else if module.was_lua_file()  { (name, "runtime"         ) }
+                    else if module.was_vivy_file() { (name, "vivy library") }
+                    else { unreachable!() }
+                }) {
+                    table.raw_set(module.as_str(), desc)?;
+                }
+                table
+            }
+        }
+
+        methods.add_method_mut("___print_info", |_, this, ()| {
+            this.should_print_infos = true;
+            Ok(())
+        });
+
+        methods.add_method("___is_already_imported", |_, this, module: String| {
+            Ok(this.loaded.borrow().contains_key(&module))
+        });
+
+        methods.add_method("___current_module", |lua, this, ()| {
+            match &*this.current_module.borrow() {
+                Some(ref current_module) => lua.create_string(current_module.as_str()).map(LuaValue::String),
+                None => {
+                    log::debug!(target: "runtime", "can't get the current module name even if vivy is initialized...");
+                    Ok(LuaValue::Nil)
+                }
+            }
+        });
+
+        methods.add_method("___run_export_module", |lua, this, name: LuaString| {
+            let table = lua.create_table()?;
+            this.jobs
+                .as_ref()
+                .map(|jobs| jobs.try_write().unwrap().export(lua, &name, &table))
+                .unwrap_or(Ok(()))?;
+            this.func
+                .as_ref()
+                .map(|func| func.try_write().unwrap().export(lua, &name, &table))
+                .unwrap_or(Ok(()))?;
+            let table = dsl::into_readonly_table(lua, table)?;
+            lua.globals().raw_set(name, table)?;
+
+            Ok(())
+        });
+
+        methods.add_method("___import", |lua, this, name: String| -> LuaResult<bool> {
+            if !name.chars().all(|c| c.is_ascii_alphanumeric()) {
+                return Err(LuaError::RuntimeError(format!(
+                    "invalid module name '{name}', must be an ascii alphanumeric string"
+                )));
+            } else if this.loading.borrow().contains(&name) {
+                return Err(LuaError::RuntimeError(format!(
+                    "circular dependency found with module '{name}'"
+                )));
+            }
+            for path in &this.path {
+                let path = PathBuf::from(path.replace('?', &name));
+                let module = Module::from_file(&name, &path);
+                if path.exists() && path.is_file() {
+                    this.loading.borrow_mut().insert(name.clone());
+                    return match lua.load(&path).set_name(path.to_string_lossy())?.exec() {
+                        Ok(()) => {
+                            let ret = module.was_lua_file();
+                            this.loaded.borrow_mut().insert(name, module);
+                            Ok(ret)
+                        }
+                        Err(err) => {
+                            this.loading.borrow_mut().remove(&name);
+                            Err(LuaError::RuntimeError(format!(
+                                "failed to load module '{name}' at location: {}\n{err}",
+                                path.to_string_lossy()
+                            )))
+                        }
+                    };
+                }
+            }
+            Err(LuaError::RuntimeError(format!(
+                "failed to find or load module named '{name}' in path: {:#?}",
+                this.path
+            )))
+        });
+
+        methods.add_method("___main", |lua, this, table: LuaTable| -> LuaResult<()> {
+            if this.main_once {
+                return Err(LuaError::RuntimeError(
+                    "multiple 'main' blocks detected".to_string(),
+                ));
+            }
+
+            // Finish setup
+            if let Some(ref options) = this.options {
+                let options = options.try_read().unwrap();
+                for option in options.iter_registered() {
+                    let value = match options.resolve(option) {
+                        Some(value) => value.to_lua(lua)?,
+                        None => LuaValue::Nil,
+                    };
+                    lua.globals().raw_set(option.as_str(), value)?;
+                }
+            }
+
+            if let Some(ref data) = this.data {
+                data.try_write().unwrap().compute_cached_tables()
+            }
+
+            // Print infos
+            if this.should_print_infos {
+                println!(" <<< Information Report >>>");
+                println!(" # Misc");
+                println!(
+                    "   - version    {}",
+                    lua.globals().get::<_, String>("_VERSION")?
+                );
+                println!(
+                    "   - packages   {}",
+                    this.format_loaded().into_iter().collect::<Vec<_>>().join(", ")
+                );
+                if let Some(options) = this.options.as_ref() {
+                    options.try_read().unwrap().print_info();
+                }
+                if let Some(data) = this.data.as_ref() {
+                    data.try_read().unwrap().print_info();
+                }
+            }
+
+            // Set options!
+            this.set_options(lua)?;
+
+            // Do the work
+            let table_last_idx = table.raw_len() - 1;
+            let (work, ret): (Vec<_>, Vec<_>) = table
+                .pairs::<LuaValue, LuaValue>()
+                .enumerate()
+                .flat_map(|(idx, pair)| {
+                    let (dest, src) = pair.ok()?;
+                    Some((idx, lua.coerce_string(dest).unwrap_or_default(), src))
+                })
+                .partition(|(idx, ..)| i64::try_from(*idx).unwrap() != table_last_idx);
+            let [(_, _, ret)] = &ret[..] else { unreachable!("invalid return statements, should only be one: {ret:#?}") };
+            let vivy_byte_code = this.create_byte_code(lua)?;
+            log::debug!(target: "runtime", "user's script associated bytecode structure: {vivy_byte_code:#?}");
+
+            let exec_lua = Vivy::setup_runtime(RuntimeOptions {
+                ass_file: this.ass_file.clone(),
+                creation_data: Some(RuntimeCreationData::ByteCode(vivy_byte_code)),
+                include_path: this.path.iter().map(PathBuf::from).collect(),
+                mode: RuntimeMode::Compute
+            })?;
+            log::debug!(target: "runtime", "created an execution state from generated bytecode: {exec_lua:?}");
+
+            let exec_graph = VivyExecGraph::try_from((lua, work, ret.clone()))?;
+            log::debug!(target: "runtime", "user's script associated execution graph: {exec_graph:#?}");
+            this.execute_graph(lua, exec_graph)
+        });
+    }
+}
diff --git a/src/Rust/vvs_lua/src/lua_wrapper.rs b/src/Rust/vvs_lua/src/lua_wrapper.rs
index 63479414..7fd968d2 100644
--- a/src/Rust/vvs_lua/src/lua_wrapper.rs
+++ b/src/Rust/vvs_lua/src/lua_wrapper.rs
@@ -2,25 +2,34 @@
 
 use crate::traits::TypedValue;
 use mlua::prelude::*;
-use std::str::FromStr;
-use vvs_ass::{
-    ASSAuxTablePtr, ASSAuxValue, ASSContainerPtr, ASSLinePtr, ASSLinesPtr, ASSPositionPtr,
-    ASSSyllabePtr, ASSSyllabesPtr, ASSType,
-};
+use serde::{Deserialize, Serialize};
+use std::{str::FromStr, sync::Arc};
+use unicode_segmentation::UnicodeSegmentation;
+use vvs_ass::{ASSAuxValue, ASSContainer, ASSContainerPtr, ASSLinePtr, ASSLines, ASSSyllabePtr, ASSSyllabes, ASSType};
 
 macro_rules! wrap_ass2lua {
-    ($($type: ident $(-> $($trait: ident),+)?
-      $(: $fields: ident -> $add_fields: expr
-        , $methods: ident -> $add_methods: expr)?
+    ($(
+        $(#[$meta:meta])*
+        struct $type: ident
+        ($fields: ident, $methods: ident)
+            -> $add_fields: expr
+            , $add_methods: expr
     );+ $(;)?) => {
-        $(wrap_ass2lua!(@@private $type $($($trait),+)? $(; $fields -> $add_fields ; $methods -> $add_methods)?);)+
+        $(wrap_ass2lua!(@@private $(#[$meta])* $type; $fields -> $add_fields ; $methods -> $add_methods);)+
     };
 
-    (@@decl $type: ident $($trait: ident),*) => {
+    (@@decl $(#[$meta:meta])* $type: ident) => {
         paste::paste! {
-            #[derive(Debug, Clone, PartialEq, $($trait),*)]
+            #[derive(Clone)]
+            $(#[$meta])*
             #[repr(transparent)]
-            pub(crate) struct [< LuaAss $type >] ([< ASS $type >]);
+            pub(crate) struct [< LuaAss $type >] (pub(crate) [< ASS $type >]);
+
+            impl std::fmt::Debug for [< LuaAss $type >] {
+                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                    self.0.fmt(f)
+                }
+            }
 
             impl [< LuaAss $type >] {
                 #[allow(dead_code)]
@@ -42,108 +51,172 @@ macro_rules! wrap_ass2lua {
         }
     };
 
-    (@@private $type: ident $($trait: ident),*) => {
-        wrap_ass2lua!(@@decl $type $($trait),*);
-        paste::paste! { impl LuaUserData for [< LuaAss $type >] { } }
-    };
-
-    (@@private $type: ident $($trait: ident),*
-     ; $fields : ident -> $add_fields : expr
-     ; $methods: ident -> $add_methods: expr) => {
-        wrap_ass2lua!(@@decl $type $($trait),*);
+    (@@private $(#[$meta:meta])* $type: ident
+        ; $fields : ident -> $add_fields : expr
+        ; $methods: ident -> $add_methods: expr
+    ) => {
+        wrap_ass2lua!(@@decl $(#[$meta])* $type);
         paste::paste! {
             impl LuaUserData for [< LuaAss $type >] {
-                fn add_fields <'lua, F: LuaUserDataFields <'lua, Self>>($fields:  &mut F) { $add_fields  }
-                fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>($methods: &mut M) { $add_methods }
+                fn add_fields <'lua, F: LuaUserDataFields <'lua, Self>>($fields:  &mut F) {
+                    $add_fields
+                }
+                fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>($methods: &mut M) {
+                    $methods.add_meta_function("_name", |lua, ()| { lua.create_string(stringify!($type)) });
+                    $add_methods
+                }
             }
         }
     };
 }
 
 macro_rules! add_method {
-    ($fields: expr => @aux) => {
-        add_method! { $fields => aux @ LuaAssAuxTablePtr }
+    ($methods: expr => @aux) => {
+        add_method! { $methods => "aux" (_lua, _this, ()) -> {
+            todo!()
+        }}
     };
 
     ($methods: expr => @copy) => {
         add_method! {
-            method $methods => "copy" (_lua, this) -> {
-                Ok(Self((&*this).borrow().clone().into_ptr()))
+            $methods => "copy" (lua, this, ()) -> {
+                Self(this.clone()).to_lua(lua)
             }
         }
     };
 
-    ($fields: expr => $field: ident @ $wrapper: ident) => {
-        add_method! { get $fields => $field @ $wrapper }
-        add_method! { set $fields => $field @ $wrapper }
+    ($methods: expr =>
+     $name: literal ($lua: ident, $this: ident, ($($arg_1: ident: $ty_1: ty)? $(, $($arg: ident: $ty: ty),+)?)) -> $expr: expr
+    ) => {
+        #[allow(unused_parens)]
+        $methods.add_method($name,
+            |$lua, Self($this), ($($arg_1)? $(, $($arg),+,)?): ($($ty_1)? $(, $($ty),+,)?)| -> LuaResult<LuaValue> { $expr }
+        )
     };
 
-    (get $fields: expr => $field: ident @ $wrapper: ident) => {
-        $fields.add_field_method_get(stringify!($field), |_, Self(this)| {
-            Ok($wrapper(this.borrow().$field.clone()))
-        });
+    (mut $methods: expr =>
+     $name: literal ($lua: ident, $this: ident, ($($arg_1: ident: $ty_1: ty)? $(, $($arg: ident: $ty: ty),+)?)) -> $expr: expr
+    ) => {
+        #[allow(unused_parens)]
+        $methods.add_method_mut($name,
+            |$lua, Self($this), ($($arg_1)? $(, $($arg),+,)?): ($($ty_1)? $(, $($ty),+,)?)| -> LuaResult<LuaValue> { $expr }
+        )
     };
+}
 
-    (set $fields: expr => $field: ident @ $wrapper: ident) => {
-        $fields.add_field_method_set(stringify!($field), |_, Self(this), $wrapper(val)| {
-            this.borrow_mut().$field = val;
-            Ok(())
-        });
+macro_rules! add_field {
+    (get $fields: expr => $this: ident . $val: ident => $expr: expr) => {
+        $fields.add_field_method_get(stringify!($val), |_, $this| Ok($expr))
     };
 
-    (method $methods: expr => $name: literal ($lua: ident, $this: ident) -> $expr: expr) => {
-        $methods.add_method($name, |$lua, Self($this), ()| $expr)
+    (set $fields: expr => $lua: ident, $this: ident . $val: ident: $ty: ty => $expr: expr) => {
+        #[allow(unused_mut)]
+        $fields.add_field_method_set(stringify!($val), |$lua, mut $this, $val: $ty| {
+            $expr;
+            Ok(())
+        })
     };
 }
 
 wrap_ass2lua! {
-    Type;
-    AuxValue;
-    AuxTablePtr -> Default:
-        _f -> {},
-        m -> {
-            add_method! { m => @copy }
-        };
-    PositionPtr:
-        _f -> {},
-        m -> {
-            add_method! { m => @copy }
-        };
-    // The ASS types
-    ContainerPtr;
-    LinePtr:
-        f -> {
-            add_method! { f => @aux }
-            add_method! { f => position @ LuaAssPositionPtr }
-        },
-        m -> {
-            add_method! { m => @copy }
-        };
-    LinesPtr:
-        f -> {
-            add_method! { f => @aux }
-            add_method! { f => position @ LuaAssPositionPtr }
-        },
-        m -> {
-            add_method! { m => @copy }
-            add_method! { method m => "push" (_lua, _this) -> { Ok(()) }}
-        };
-    SyllabePtr:
-        f -> {
-            add_method! { f => @aux }
-            add_method! { f => position @ LuaAssPositionPtr }
-        },
-        m -> {
-            add_method! { m => @copy }
-        };
-    SyllabesPtr:
-        f -> {
-            add_method! { f => @aux }
-            add_method! { f => position @ LuaAssPositionPtr }
-        },
-        m -> {
-            add_method! { m => @copy }
-        };
+    #[doc = "Wraps the ASSType for internal usage, won't be exposed to the user"]
+    #[derive(PartialEq, Eq, Serialize, Deserialize)]
+    struct Type (_f, _m) -> {}, {};
+
+    #[doc = "Wraps the ASSAuxValue for internal usage, won't be exposed to the user"]
+    #[derive(PartialEq, Serialize, Deserialize)]
+    struct AuxValue (_f, _m) -> {}, {};
+
+    #[doc = "Wraps the ASSContainerPtr for internal usage, won't be exposed to the user"]
+    struct ContainerPtr (_f, _m) -> {}, {};
+
+    #[doc = "Wraps the ASSLines container to expose it to lua code"]
+    #[derive(Default, PartialEq)]
+    struct Lines (_f, m) -> {}, {
+        add_method! { m => @copy }
+        add_method! { mut m => "push" (_lua, this, (item: LuaValue)) -> match item {
+            LuaNil => {
+                log::warn!(target: "lua", "pushing nothing into Lines...");
+                Ok(LuaNil)
+            }
+            LuaValue::Table(items) => {
+                this.extend(items.sequence_values::<LuaAssLinePtr>().flat_map(|line| Some(line.ok()?.0)));
+                Ok(LuaNil)
+            }
+            LuaValue::UserData(item) if item.is::<LuaAssLinePtr>() => {
+                this.push(item.take::<LuaAssLinePtr>().unwrap().0);
+                Ok(LuaNil)
+            }
+            LuaValue::UserData(item) if item.is::<LuaAssLines>() => {
+                this.extend(item.take::<LuaAssLines>().unwrap().0);
+                Ok(LuaNil)
+            }
+            _ => Err(LuaError::RuntimeError(format!("expected a Line or a table of Line to push into lines, got {}", item.type_name())))
+        }}
+
+        m.add_meta_method(LuaMetaMethod::Pairs, Self::pairs);
+        m.add_meta_method(LuaMetaMethod::IPairs, Self::pairs);
+    };
+
+    #[doc = "Wraps the ASSSyllabes container to expose it to lua code"]
+    #[derive(Default, PartialEq)]
+    struct Syllabes (_f, m) -> {}, {
+        add_method! { m => @copy }
+        add_method! { mut m => "push" (_lua, this, (item: LuaValue)) -> match item {
+            LuaNil => {
+                log::warn!(target: "lua", "pushing nothing into Syllabes...");
+                Ok(LuaNil)
+            }
+            LuaValue::Table(items) => {
+                this.extend(items.sequence_values::<LuaAssSyllabePtr>().flat_map(|line| Some(line.ok()?.0)));
+                Ok(LuaNil)
+            }
+            LuaValue::UserData(item) if item.is::<LuaAssSyllabePtr>() => {
+                this.push(item.take::<LuaAssSyllabePtr>().unwrap().0);
+                Ok(LuaNil)
+            }
+            LuaValue::UserData(item) if item.is::<LuaAssSyllabes>() => {
+                this.extend(item.take::<LuaAssSyllabes>().unwrap().0);
+                Ok(LuaNil)
+            }
+            _ => Err(LuaError::RuntimeError(format!("expected a Syllabe or a table of Syllabe to push into lines, got {}", item.type_name())))
+        }}
+
+        m.add_meta_method(LuaMetaMethod::Pairs, Self::pairs);
+        m.add_meta_method(LuaMetaMethod::IPairs, Self::pairs);
+    };
+
+    #[doc = "Wraps the ASSLinePtr to expose it to lua code"]
+    #[derive(Default)]
+    struct LinePtr (f, m) -> {
+        add_field! { get f => this.start => this.0.0.try_read().unwrap().start }
+        add_field! { get f => this.fini  => this.0.0.try_read().unwrap().fini  }
+
+        add_field! { set f => lua, this.start: LuaValue => this.0.0.try_write().unwrap().start = lua.coerce_integer(start)?.unwrap_or_default() }
+        add_field! { set f => lua, this.fini:  LuaValue => this.0.0.try_write().unwrap().fini  = lua.coerce_integer(fini)?.unwrap_or_default()  }
+    }, {
+        add_method! { m => @aux }
+        add_method! { m => @copy }
+
+        m.add_meta_method(LuaMetaMethod::Pairs, Self::pairs);
+        m.add_meta_method(LuaMetaMethod::IPairs, Self::pairs);
+    };
+
+    #[doc = "Wraps the ASSSyllabePtr to expose it to lua code"]
+    #[derive(Default)]
+   struct SyllabePtr (f, m) -> {
+        add_field! { get f => this.start => this.0.0.try_read().unwrap().start }
+        add_field! { get f => this.fini  => this.0.0.try_read().unwrap().fini  }
+
+        add_field! { set f => lua, this.start: LuaValue => this.0.0.try_write().unwrap().start = lua.coerce_integer(start)?.unwrap_or_default() }
+        add_field! { set f => lua, this.fini:  LuaValue => this.0.0.try_write().unwrap().fini  = lua.coerce_integer(fini)?.unwrap_or_default()  }
+    }, {
+        add_method! { m => @aux }
+        add_method! { m => @copy }
+
+        m.add_meta_method(LuaMetaMethod::Pairs, Self::pairs);
+        m.add_meta_method(LuaMetaMethod::IPairs, Self::pairs);
+    };
 }
 
 impl TypedValue for LuaAssAuxValue {
@@ -156,7 +229,7 @@ impl FromStr for LuaAssType {
     type Err = String;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Ok(Self(s.parse().map_err(|err| format!("{err}"))?))
+        Ok(Self(s.parse()?))
     }
 }
 
@@ -165,8 +238,74 @@ impl<'lua> TryFrom<LuaString<'lua>> for LuaAssType {
 
     fn try_from(value: LuaString<'lua>) -> Result<Self, Self::Error> {
         match value.to_str() {
-            Ok(bt) => Ok(Self(bt.parse().map_err(|err| format!("{err}"))?)),
+            Ok(bt) => Ok(Self(bt.parse()?)),
             _ => Err("can't build a vvs_lua::BaseType from an invalid utf8 string".to_string()),
         }
     }
 }
+
+impl LuaAssLinePtr {
+    fn pairs<'lua>(lua: &'lua Lua, this: &Self, _: ()) -> LuaResult<LuaValue<'lua>> {
+        let ptr = this.0 .0.clone();
+        let (mut i, n) = (Arc::new(0), ptr.try_read().unwrap().content.len());
+        Ok(LuaValue::Function(lua.create_function_mut(move |lua, ()| {
+            *Arc::get_mut(&mut i).unwrap() += 1;
+            if *i <= n {
+                if let Some(item) = ptr.try_read().unwrap().content.get(*i - 1) {
+                    return (*i, LuaAssSyllabePtr(item.clone())).to_lua_multi(lua);
+                }
+            }
+            LuaNil.to_lua_multi(lua)
+        })?))
+    }
+}
+
+impl LuaAssLines {
+    fn pairs<'lua>(lua: &'lua Lua, this: &Self, _: ()) -> LuaResult<LuaValue<'lua>> {
+        let (mut i, n) = (Arc::new(0), this.0.len());
+        Ok(LuaValue::Function(lua.create_function_mut(move |lua, ()| {
+            *Arc::get_mut(&mut i).unwrap() += 1;
+            if *i <= n {
+                todo!()
+            } else {
+                LuaNil.to_lua_multi(lua)
+            }
+        })?))
+    }
+}
+
+impl LuaAssSyllabePtr {
+    fn pairs<'lua>(lua: &'lua Lua, this: &Self, _: ()) -> LuaResult<LuaValue<'lua>> {
+        let ptr = this.0 .0.clone();
+        let (mut i, n) = (Arc::new(0), ptr.try_read().unwrap().content.len());
+        Ok(LuaValue::Function(lua.create_function_mut(move |lua, ()| {
+            *Arc::get_mut(&mut i).unwrap() += 1;
+            if *i <= n {
+                if let Some(item) = ptr.try_read().unwrap().content.graphemes(true).nth(*i - 1) {
+                    return (*i, item).to_lua_multi(lua);
+                }
+            }
+            LuaNil.to_lua_multi(lua)
+        })?))
+    }
+}
+
+impl LuaAssSyllabes {
+    fn pairs<'lua>(lua: &'lua Lua, this: &Self, _: ()) -> LuaResult<LuaValue<'lua>> {
+        let (mut i, n) = (Arc::new(0), this.0.len());
+        Ok(LuaValue::Function(lua.create_function_mut(move |lua, ()| {
+            *Arc::get_mut(&mut i).unwrap() += 1;
+            if *i <= n {
+                todo!()
+            } else {
+                LuaNil.to_lua_multi(lua)
+            }
+        })?))
+    }
+}
+
+impl From<ASSContainer> for LuaAssContainerPtr {
+    fn from(value: ASSContainer) -> Self {
+        Self(ASSContainerPtr(vvs_ass::ptr!(value)))
+    }
+}
diff --git a/src/Rust/vvs_lua/src/options/actions.rs b/src/Rust/vvs_lua/src/options/actions.rs
index ca0f4c97..ad3d33d3 100644
--- a/src/Rust/vvs_lua/src/options/actions.rs
+++ b/src/Rust/vvs_lua/src/options/actions.rs
@@ -41,10 +41,7 @@ pub(crate) struct NamedSetOptionValue {
 
 impl SetOptionValue {
     pub fn named(self, name: String) -> NamedSetOptionValue {
-        NamedSetOptionValue {
-            name,
-            value: self.value,
-        }
+        NamedSetOptionValue { name, value: self.value }
     }
 
     pub fn is_none(&self) -> bool {
@@ -54,16 +51,12 @@ impl SetOptionValue {
 
 impl RegisterOptionValue {
     pub fn named(self, name: String) -> NamedRegisterOptionValue {
-        NamedRegisterOptionValue {
-            name,
-            doc: self.doc,
-            ty: self.ty,
-            value: self.value,
-        }
+        NamedRegisterOptionValue { name, doc: self.doc, ty: self.ty, value: self.value }
     }
 }
 
 impl NamedSetOptionValue {
+    #[allow(dead_code)]
     pub fn is_none(&self) -> bool {
         self.value.is_none()
     }
@@ -133,9 +126,7 @@ impl<'lua> FromLua<'lua> for RegisterOptionValue {
             _ => Err(LuaError::FromLuaConversionError {
                 from: lua_value.type_name(),
                 to: "RegisterOptionValue",
-                message: Some(
-                    "expected a string or a table with the 'option' instruction".to_string(),
-                ),
+                message: Some("expected a string or a table with the 'option' instruction".to_string()),
             }),
         }
     }
diff --git a/src/Rust/vvs_lua/src/options/register.rs b/src/Rust/vvs_lua/src/options/register.rs
index a58f7eef..00f18c2c 100644
--- a/src/Rust/vvs_lua/src/options/register.rs
+++ b/src/Rust/vvs_lua/src/options/register.rs
@@ -6,7 +6,10 @@ use crate::{
     values::Value,
 };
 use mlua::prelude::*;
-use std::{cell::RefCell, collections::HashMap, rc::Rc};
+use std::{
+    collections::HashMap,
+    sync::{Arc, RwLock},
+};
 
 use super::actions::{NamedRegisterOptionValue, NamedSetOptionValue};
 
@@ -18,28 +21,22 @@ pub(crate) struct VivyOptionRegister {
     toml_overrides: TomlOptions,
 }
 
-pub(crate) type VivyOptionRegisterPtr = Rc<RefCell<VivyOptionRegister>>;
+pub(crate) type VivyOptionRegisterPtr = Arc<RwLock<VivyOptionRegister>>;
 
 impl VivyOptionRegister {
+    /// Create a new option register.
     pub fn new(toml_overrides: TomlOptions) -> VivyOptionRegisterPtr {
-        Rc::new(RefCell::new(Self {
-            toml_overrides,
-            ..Default::default()
-        }))
+        Arc::new(RwLock::new(Self { toml_overrides, ..Default::default() }))
     }
 
     /// Iter over the registered option names.
-    pub fn iter_registered(
-        &self,
-    ) -> std::collections::hash_map::Keys<String, NamedRegisterOptionValue> {
+    pub fn iter_registered(&self) -> std::collections::hash_map::Keys<String, NamedRegisterOptionValue> {
         self.registered.keys()
     }
 
     /// Returns toml values that where defined in the passed toml file but where not declared in
     /// the loaded scripts.
-    pub(crate) fn iter_undefined_toml_options(
-        &self,
-    ) -> impl Iterator<Item = (&str, TomlOptionsValue)> {
+    pub(crate) fn iter_undefined_toml_options(&self) -> impl Iterator<Item = (&str, TomlOptionsValue)> {
         self.toml_overrides
             .iter()
             .filter(|(name, _)| !self.registered.contains_key(*name))
@@ -49,9 +46,7 @@ impl VivyOptionRegister {
     /// return [None].
     pub fn get_docstring<S: AsRef<str>>(&self, name: S) -> Option<impl IntoIterator<Item = &str>> {
         match self.registered.get(name.as_ref()) {
-            Some(NamedRegisterOptionValue { doc, .. }) => {
-                doc.as_ref().map(|str| str.lines().map(|line| line.trim()))
-            }
+            Some(NamedRegisterOptionValue { doc, .. }) => doc.as_ref().map(|str| str.lines().map(|line| line.trim())),
             None => {
                 log::error!(target: "lua", "option {} not defined!", name.as_ref());
                 None
@@ -63,12 +58,9 @@ impl VivyOptionRegister {
     /// defined we return [None].
     pub fn get_type<S: AsRef<str>>(&self, name: S) -> Option<Type> {
         match self.registered.get(name.as_ref()) {
-            Some(NamedRegisterOptionValue { ty, value, .. }) => value
-                .as_ref()
-                .map(TypedValue::ty)
-                .into_iter()
-                .chain(*ty)
-                .next(),
+            Some(NamedRegisterOptionValue { ty, value, .. }) => {
+                value.as_ref().map(TypedValue::ty).into_iter().chain(*ty).next()
+            }
             None => {
                 log::error!(target: "lua", "option {} not defined!", name.as_ref());
                 None
@@ -82,29 +74,30 @@ impl VivyOptionRegister {
     /// The following are checked in order:
     /// - is the option overriden by the user with a `set { name, val = ... }` instruction?
     /// - is the option overriden in the toml file?
+    /// - if the option should have been in the toml file and is not then returns [None].
     /// - returns the default value of the option if it was declared with one
     ///
     /// Note: if the option is not defined or no value is found, [None] will be returned.
     pub fn resolve<S: AsRef<str>>(&self, name: S) -> Option<Value> {
         let name = name.as_ref();
         let NamedRegisterOptionValue { ty, value, .. } = self.registered.get(name)?;
-
         if let Some(NamedSetOptionValue { value, .. }) = self.user_overrides.get(name) {
             Self::check_type(ty, value.as_ref().map(|value| value as &dyn TypedValue))?;
             if let Some(value) = value {
                 log::debug!(target: "lua", "returning the user override for option '{name}'");
-                return Some(value.clone());
+                Some(value.clone())
+            } else if let Some(option) = self.toml_overrides.get(name) {
+                Self::check_type(ty, Some(&option as &dyn TypedValue))?;
+                log::debug!(target: "lua", "returning the toml override for option '{name}'");
+                Some(option.to_value())
+            } else {
+                log::debug!(target: "lua", "the option '{name}' should have been in the toml file but is nowhere to be found");
+                None
             }
+        } else {
+            log::debug!(target: "lua", "returning the original declared value for option '{name}'");
+            value.clone()
         }
-
-        if let Some(option) = self.toml_overrides.get(name) {
-            Self::check_type(ty, Some(&option as &dyn TypedValue))?;
-            log::debug!(target: "lua", "returning the toml override for option '{name}'");
-            return Some(option.to_value());
-        }
-
-        log::debug!(target: "lua", "returning the original declared value for option '{name}'");
-        value.clone()
     }
 
     /// Register an user override for an option. A double register is an error if the previous
@@ -112,27 +105,16 @@ impl VivyOptionRegister {
     /// Registering an option that was not declared is an error.
     ///
     /// Note: The following code is valid in Lua: `set "a"; set { "a", value = 1 }`
-    fn register_user_override(
-        &mut self,
-        name: String,
-        value: SetOptionValue,
-    ) -> Result<(), String> {
+    fn register_user_override(&mut self, name: String, value: SetOptionValue) -> Result<(), String> {
         if self.registered.get(&name).is_none() {
             return Err(format!(
                 "can't register option '{name}' as it was not previously declared"
             ));
         }
 
-        if let Some(
-            old_value @ NamedSetOptionValue {
-                value: Some(old), ..
-            },
-        ) = self.user_overrides.get(&name)
-        {
-            if !(old_value.is_none() || value.is_none()) {
-                return Err(format!(
-                    "override already present for option '{name}': {old}"
-                ));
+        if let Some(NamedSetOptionValue { value: Some(old), .. }) = self.user_overrides.get(&name) {
+            if !value.is_none() {
+                return Err(format!("override already present for option '{name}': {old}"));
             }
         }
 
@@ -147,16 +129,18 @@ impl VivyOptionRegister {
 
         if let Some(old) = self.registered.get(&value.name) {
             if PartialEq::ne(old, &value) && (!old.is_none()) {
-                return Err(format!("option '{}' was already registered\n- old value is {old:?}\n- new value is {value:?}", value.name));
+                return Err(format!(
+                    "option '{}' was already registered\n- old value is {old:?}\n- new value is {value:?}",
+                    value.name
+                ));
             }
         }
-        Self::check_type(
-            &value.ty,
-            value.value.as_ref().map(|val| val as &dyn TypedValue),
-        )
-        .ok_or_else(|| {
+        Self::check_type(&value.ty, value.value.as_ref().map(|val| val as &dyn TypedValue)).ok_or_else(|| {
             let (ty, val_ty) = (value.ty.unwrap(), value.value.as_ref().unwrap().ty());
-            format!("option '{}' was declared with type '{ty}' but was defaulted with value of type '{val_ty}'", value.name)
+            format!(
+                "option '{}' was declared with type '{ty}' but was defaulted with value of type '{val_ty}'",
+                value.name
+            )
         })?;
         log::debug!(target: "lua", "register option {} with: {value:?}", value.name);
         self.registered.insert(value.name.to_string(), value);
@@ -186,7 +170,7 @@ impl VivyOptionRegister {
                     .map(|opt| format!(" : {} = {opt}", opt.ty()))
                     .unwrap_or_else(|| {
                         self.get_type(name)
-                            .map(|ty| format!(" : {ty}"))
+                            .map(|ty| format!(" : {ty} = !"))
                             .unwrap_or(" : nil".to_string())
                     });
                 let doc = self
@@ -212,10 +196,7 @@ impl VivyOptionRegister {
 
 impl LuaUserData for VivyOptionRegister {
     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
-        methods.add_method("resolve", |_, this, name: String| {
-            this.resolve(&name)
-                .ok_or(LuaError::RuntimeError(format!("option '{name}' not found")))
-        });
+        methods.add_meta_function("_name", |lua, ()| lua.create_string("VivyOptionRegister"));
 
         methods.add_method_mut("will_set", |_, this, name: String| {
             this.register_user_override(name, SetOptionValue::default())
@@ -228,16 +209,11 @@ impl LuaUserData for VivyOptionRegister {
         });
 
         methods.add_method_mut("set", |_, this, (name, value): (String, SetOptionValue)| {
-            this.register_user_override(name, value)
-                .map_err(LuaError::RuntimeError)
+            this.register_user_override(name, value).map_err(LuaError::RuntimeError)
         });
 
-        methods.add_method_mut(
-            "register",
-            |_, this, (name, value): (String, RegisterOptionValue)| {
-                this.register_option(name, value)
-                    .map_err(LuaError::RuntimeError)
-            },
-        );
+        methods.add_method_mut("register", |_, this, (name, value): (String, RegisterOptionValue)| {
+            this.register_option(name, value).map_err(LuaError::RuntimeError)
+        });
     }
 }
diff --git a/src/Rust/vvs_lua/src/toml_option.rs b/src/Rust/vvs_lua/src/toml_option.rs
index 5312c4a6..fb77a742 100644
--- a/src/Rust/vvs_lua/src/toml_option.rs
+++ b/src/Rust/vvs_lua/src/toml_option.rs
@@ -40,32 +40,19 @@ impl TomlOptions {
             .map(|(name, value)| (name.as_str(), TomlOptionsValue(value)))
     }
 
-    fn verify_toml_table(
-        table: &toml::map::Map<String, toml::Value>,
-    ) -> Result<(), TomlOptionsError> {
+    fn verify_toml_table(table: &toml::map::Map<String, toml::Value>) -> Result<(), TomlOptionsError> {
         for (name, value) in table {
             match value {
-                toml::Value::Datetime(_) => {
-                    return Err(TomlOptionsError::InvalidValue(name.clone(), "Datetime"))
-                }
-                toml::Value::Table(_) => {
-                    return Err(TomlOptionsError::InvalidValue(name.clone(), "Table"))
-                }
+                toml::Value::Datetime(_) => return Err(TomlOptionsError::InvalidValue(name.clone(), "Datetime")),
+                toml::Value::Table(_) => return Err(TomlOptionsError::InvalidValue(name.clone(), "Table")),
                 toml::Value::Array(array) => {
                     for value in array {
                         match value {
                             toml::Value::Datetime(_) => {
-                                return Err(TomlOptionsError::InvalidArray(
-                                    name.clone(),
-                                    "Datetime",
-                                ))
-                            }
-                            toml::Value::Array(_) => {
-                                return Err(TomlOptionsError::InvalidArray(name.clone(), "Array"))
-                            }
-                            toml::Value::Table(_) => {
-                                return Err(TomlOptionsError::InvalidArray(name.clone(), "Table"))
+                                return Err(TomlOptionsError::InvalidArray(name.clone(), "Datetime"))
                             }
+                            toml::Value::Array(_) => return Err(TomlOptionsError::InvalidArray(name.clone(), "Array")),
+                            toml::Value::Table(_) => return Err(TomlOptionsError::InvalidArray(name.clone(), "Table")),
                             _ => {}
                         }
                     }
diff --git a/src/Rust/vvs_lua/src/traits.rs b/src/Rust/vvs_lua/src/traits.rs
index 0901ec53..08e2b56c 100644
--- a/src/Rust/vvs_lua/src/traits.rs
+++ b/src/Rust/vvs_lua/src/traits.rs
@@ -1,7 +1,7 @@
 //! General traits
 
-use crate::{libs::Vivy, types::*};
-use vvs_ass::{ptr, ASSLine, ASSLines, ASSSyllabe, ASSSyllabes, Ptr};
+use crate::types::*;
+use vvs_ass::{ptr, Ptr};
 
 /// A trait for typed values that can be used to pass informations between Lua and Rust code.
 pub trait TypedValue {
@@ -41,7 +41,7 @@ pub trait FromSingleElement {
     type SingleElement;
 
     /// Instanciate an element from a single child.
-    fn from_single_element(vivy: &Vivy, single: Self::SingleElement) -> Self;
+    fn from_single_element(single: Self::SingleElement) -> Self;
 }
 
 impl<T: FromSingleElement> FromSingleElement for Ptr<T>
@@ -50,28 +50,9 @@ where
 {
     type SingleElement = Ptr<<T as FromSingleElement>::SingleElement>;
 
-    fn from_single_element(vivy: &Vivy, single: Self::SingleElement) -> Self {
+    fn from_single_element(single: Self::SingleElement) -> Self {
         ptr!(FromSingleElement::from_single_element(
-            vivy,
-            (*single.borrow()).clone()
+            (*single.try_read().unwrap()).clone()
         ))
     }
 }
-
-macro_rules! impl_from_single_elem {
-    ($from: ident => $to: ident) => {
-        impl FromSingleElement for paste::paste! { [< ASS $to >] } {
-            type SingleElement = paste::paste! { [< ASS $from >] };
-            fn from_single_element(vivy: &Vivy, single: Self::SingleElement) -> Self {
-                Self {
-                    position: single.position.clone(),
-                    content: vec![ptr!(single)],
-                    aux: vivy.new_ass_aux_table(vvs_ass::ASSType::$to),
-                }
-            }
-        }
-    };
-}
-
-impl_from_single_elem! { Line => Lines }
-impl_from_single_elem! { Syllabe => Syllabes }
diff --git a/src/Rust/vvs_lua/src/types.rs b/src/Rust/vvs_lua/src/types.rs
index 38c60f82..6fcfafc3 100644
--- a/src/Rust/vvs_lua/src/types.rs
+++ b/src/Rust/vvs_lua/src/types.rs
@@ -11,7 +11,7 @@ pub enum BaseType {
 }
 
 /// Types that are exposed for interactions between Lua and Rust code.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, PartialEq, Eq)]
 pub enum Type {
     Base(BaseType),
 
@@ -21,6 +21,15 @@ pub enum Type {
     Array,
 }
 
+impl std::fmt::Debug for Type {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Base(arg0) => arg0.fmt(f),
+            Self::Array => write!(f, "Array"),
+        }
+    }
+}
+
 impl AsRef<str> for Type {
     fn as_ref(&self) -> &str {
         match self {
diff --git a/src/Rust/vvs_lua/src/values.rs b/src/Rust/vvs_lua/src/values.rs
index 777cae15..594bcbbd 100644
--- a/src/Rust/vvs_lua/src/values.rs
+++ b/src/Rust/vvs_lua/src/values.rs
@@ -3,9 +3,10 @@
 use crate::{traits::TypedValue, types::*};
 use mlua::prelude::*;
 use std::fmt::Write;
+use vvs_ass::ASSAuxValue;
 
 /// The base values that can be passed to/from Lua code.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, PartialEq)]
 pub enum BaseValue {
     Integer(i64),
     Floating(f64),
@@ -14,12 +15,32 @@ pub enum BaseValue {
 }
 
 /// The values that can be passed to/from Lua code.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, PartialEq)]
 pub enum Value {
     Base(BaseValue),
     Array(Vec<BaseValue>),
 }
 
+impl std::fmt::Debug for BaseValue {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Integer(arg0) => write!(f, "{arg0}"),
+            Self::Floating(arg0) => write!(f, "{arg0}"),
+            Self::String(arg0) => write!(f, "{arg0:?}"),
+            Self::Boolean(arg0) => write!(f, "{arg0}"),
+        }
+    }
+}
+
+impl std::fmt::Debug for Value {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Base(arg0) => write!(f, "{arg0:?}"),
+            Self::Array(arg0) => write!(f, "{arg0:?}"),
+        }
+    }
+}
+
 impl AsRef<str> for BaseType {
     fn as_ref(&self) -> &str {
         match self {
@@ -60,8 +81,7 @@ impl TryFrom<&toml::Value> for Value {
             toml::Value::Float(number) => Ok(Self::Base(BaseValue::Floating(*number))),
             toml::Value::Boolean(boolean) => Ok(Self::Base(BaseValue::Boolean(*boolean))),
             toml::Value::Array(vals) => {
-                let (vals, errors): (Vec<_>, Vec<_>) =
-                    vals.iter().map(Value::try_from).partition(Result::is_ok);
+                let (vals, errors): (Vec<_>, Vec<_>) = vals.iter().map(Value::try_from).partition(Result::is_ok);
                 if !errors.is_empty() {
                     errors
                         .into_iter()
@@ -122,8 +142,7 @@ impl<'lua> FromLua<'lua> for Value {
             | base @ LuaValue::String(_) => Ok(Self::Base(BaseValue::from_lua(base, lua)?)),
 
             LuaValue::Table(vals) => {
-                let (vals, errors): (Vec<_>, Vec<_>) =
-                    vals.sequence_values::<BaseValue>().partition(Result::is_ok);
+                let (vals, errors): (Vec<_>, Vec<_>) = vals.sequence_values::<BaseValue>().partition(Result::is_ok);
                 if let Some(err) = errors.into_iter().map(Result::unwrap_err).next() {
                     return Err(err);
                 }
@@ -183,3 +202,25 @@ impl std::fmt::Display for Value {
         }
     }
 }
+
+impl From<BaseValue> for ASSAuxValue {
+    fn from(value: BaseValue) -> Self {
+        match value {
+            BaseValue::Floating(val) => ASSAuxValue::Floating(val),
+            BaseValue::Integer(val) => ASSAuxValue::Integer(val),
+            BaseValue::Boolean(val) => ASSAuxValue::Boolean(val),
+            BaseValue::String(val) => ASSAuxValue::String(val),
+        }
+    }
+}
+
+impl TryInto<ASSAuxValue> for Value {
+    type Error = String;
+
+    fn try_into(self) -> Result<ASSAuxValue, Self::Error> {
+        match self {
+            Value::Base(base) => Ok(base.into()),
+            Value::Array(_) => Err("can't convert an array value into an ASSAuxValue".to_string()),
+        }
+    }
+}
diff --git a/src/Rust/vvs_procmacro/src/lib.rs b/src/Rust/vvs_procmacro/src/lib.rs
index 3d267f43..64bbd40d 100644
--- a/src/Rust/vvs_procmacro/src/lib.rs
+++ b/src/Rust/vvs_procmacro/src/lib.rs
@@ -1,10 +1,13 @@
+#![forbid(unsafe_code)]
+
 use proc_macro::TokenStream;
 use proc_macro2::*;
 use quote::*;
+use syn::{parse_macro_input, DeriveInput};
 
 #[proc_macro_derive(EnumVariantCount)]
 pub fn derive_enum_variant_count(input: TokenStream) -> TokenStream {
-    let syn_item = syn::parse::<syn::DeriveInput>(input).expect("failed to parse input with syn");
+    let syn_item = parse_macro_input!(input as DeriveInput);
     let (len, name) = match syn_item.data {
         syn::Data::Enum(enum_item) => (
             enum_item.variants.len(),
@@ -20,7 +23,7 @@ pub fn derive_enum_variant_count(input: TokenStream) -> TokenStream {
 
 #[proc_macro_derive(EnumVariantIter)]
 pub fn derive_enum_variant_iter(input: TokenStream) -> TokenStream {
-    let syn_item = syn::parse::<syn::DeriveInput>(input).expect("failed to parse input with syn");
+    let syn_item = parse_macro_input!(input as DeriveInput);
     let (enum_name, name, content) = match syn_item.data {
         syn::Data::Enum(enum_item) => (
             syn_item.ident.clone(),
@@ -43,3 +46,40 @@ pub fn derive_enum_variant_iter(input: TokenStream) -> TokenStream {
     };
     quote! { pub const #name : &[#enum_name] = #content; }.into()
 }
+
+#[proc_macro_derive(EnumVariantFromStr)]
+pub fn derive_enum_variant_from_str(input: TokenStream) -> TokenStream {
+    let syn_item = parse_macro_input!(input as DeriveInput);
+    let enum_item = match syn_item.data {
+        syn::Data::Enum(enum_item) => enum_item,
+        _ => panic!("EnumVariantFromStr only works on Enums"),
+    };
+    let name = syn_item.ident;
+    let match_branches = enum_item
+        .variants
+        .into_iter()
+        .map(|variant| {
+            format!(
+                "{:?} => Ok({name}::{}),",
+                variant.ident.to_string().to_lowercase(),
+                variant.ident
+            )
+        })
+        .chain(Some("_ => Err(format!(\"unknown field '{{s}}'\")),".to_string()))
+        .collect::<Vec<_>>()
+        .join("");
+    let match_branches = syn::parse_str::<syn::Expr>(&format!(
+        "match s.trim().to_lowercase().as_str() {{ {match_branches} }}"
+    ))
+    .expect("failed generation of enum's FromStr implementation");
+    quote! {
+        impl std::str::FromStr for #name {
+            type Err = std::string::String;
+            fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+                #match_branches
+            }
+
+        }
+    }
+    .into()
+}
diff --git a/src/Rust/vvs_repl/Cargo.toml b/src/Rust/vvs_repl/Cargo.toml
index 14ec9b1a..8ef9155b 100644
--- a/src/Rust/vvs_repl/Cargo.toml
+++ b/src/Rust/vvs_repl/Cargo.toml
@@ -8,10 +8,9 @@ description = "REPL implementation for VVS"
 
 [dependencies]
 log.workspace = true
+mlua.workspace = true
 thiserror.workspace = true
 
-vvs_lua = { path = "../vvs_lua" }
-
 rustyline = { version = "^11", default-features = false, features = [
     "case_insensitive_history_search",
 ] }
diff --git a/src/Rust/vvs_repl/src/error.rs b/src/Rust/vvs_repl/src/error.rs
index 3866f72c..e9f379ba 100644
--- a/src/Rust/vvs_repl/src/error.rs
+++ b/src/Rust/vvs_repl/src/error.rs
@@ -1,9 +1,10 @@
+use mlua::prelude::*;
 use thiserror::Error;
 
 #[derive(Debug, Error)]
 pub enum ReplError {
-    Lua(vvs_lua::mlua::Error),
-    Prompt(&'static str, vvs_lua::mlua::Error),
+    Lua(LuaError),
+    Prompt(&'static str, LuaError),
     ReadLine(rustyline::error::ReadlineError),
 }
 
diff --git a/src/Rust/vvs_repl/src/lib.rs b/src/Rust/vvs_repl/src/lib.rs
index 6b271382..44e293c1 100644
--- a/src/Rust/vvs_repl/src/lib.rs
+++ b/src/Rust/vvs_repl/src/lib.rs
@@ -1,9 +1,10 @@
 //! The REPL for VivyScript
+#![forbid(unsafe_code)]
 
 use crate::{error::ReplError, tables::*};
+use mlua::prelude::*;
 use rustyline::{error::ReadlineError, history::DefaultHistory, Editor};
 use std::sync::atomic::AtomicBool;
-use vvs_lua::mlua::prelude::*;
 
 mod error;
 mod tables;
@@ -24,31 +25,28 @@ impl REPL {
     }
 
     fn set_prompt_multiline(&self) {
-        self.prompt_multiline
-            .store(true, std::sync::atomic::Ordering::Relaxed);
+        self.prompt_multiline.store(true, std::sync::atomic::Ordering::Relaxed);
     }
 
     fn set_prompt_singleline(&self) {
-        self.prompt_multiline
-            .store(false, std::sync::atomic::Ordering::Relaxed);
+        self.prompt_multiline.store(false, std::sync::atomic::Ordering::Relaxed);
     }
 
     fn get_prompt(&self) -> Result<String, ReplError> {
-        let mut packages = vvs_lua::get_loaded(&self.lua).map_err(|lua| {
-            ReplError::Prompt("failed to get the load path from the vivy runtime", lua)
-        })?;
+        let mut packages: Vec<String> = self
+            .lua
+            .load(mlua::chunk! { vivy:___get_loaded() })
+            .eval()
+            .map_err(|lua| ReplError::Prompt("failed to get the load path from the vivy runtime", lua))?;
         packages.sort();
 
-        let prompt = match self
-            .prompt_multiline
-            .load(std::sync::atomic::Ordering::Relaxed)
-        {
+        let prompt = match self.prompt_multiline.load(std::sync::atomic::Ordering::Relaxed) {
             true => ">>",
             false => ">",
         };
 
         Ok(if packages.is_empty() {
-            format!("vivy {}", prompt)
+            format!("vivy {} ", prompt)
         } else {
             format!("vivy {{ {} }} {} ", packages.join(", "), prompt)
         })
@@ -80,8 +78,17 @@ impl REPL {
             LuaValue::String(string) => format!("{string:?}"),
             LuaValue::Table(table) => {
                 let inner = table.pairs::<LuaValue, LuaValue>().flat_map(|pair| {
-                    pair.ok()
-                        .map(|(key, value)| (TableKey(key), TableValue(value.type_name())))
+                    pair.ok().map(|(key, value)| {
+                        let value = match value {
+                            LuaNil => "nil".to_string(),
+                            LuaValue::Boolean(boolean) => format!("{boolean}"),
+                            LuaValue::Integer(num) => format!("{num}"),
+                            LuaValue::Number(num) => format!("{num}"),
+                            LuaValue::String(str) => format!("{:?}", str.to_string_lossy()),
+                            value => value.type_name().to_string(),
+                        };
+                        (TableKey(key), TableValue(value))
+                    })
                 });
                 format!("{}", TableFormatter(inner.collect()))
             }
@@ -91,11 +98,7 @@ impl REPL {
             }
             _ => value.type_name().to_string(),
         };
-        values
-            .into_iter()
-            .map(handle_value)
-            .collect::<Vec<_>>()
-            .join(", ")
+        values.into_iter().map(handle_value).collect::<Vec<_>>().join(", ")
     }
 
     /// Handle a command sent by the user. Returns an error if the readline or the lua encountered
@@ -129,10 +132,7 @@ impl REPL {
                     return Ok(false);
                 }
 
-                Err(LuaError::SyntaxError {
-                    incomplete_input: true,
-                    ..
-                }) => {
+                Err(LuaError::SyntaxError { incomplete_input: true, .. }) => {
                     // continue reading input and append it to `line`
                     line.push('\n'); // separate input lines
                     self.set_prompt_multiline();
diff --git a/src/Rust/vvs_repl/src/tables.rs b/src/Rust/vvs_repl/src/tables.rs
index f3825a61..3d970ffb 100644
--- a/src/Rust/vvs_repl/src/tables.rs
+++ b/src/Rust/vvs_repl/src/tables.rs
@@ -1,7 +1,7 @@
-use vvs_lua::mlua::prelude::*;
+use mlua::prelude::*;
 
 pub(crate) struct TableKey<'lua>(pub LuaValue<'lua>);
-pub(crate) struct TableValue(pub &'static str);
+pub(crate) struct TableValue(pub String);
 pub(crate) struct TableFormatter<'lua>(pub Vec<(TableKey<'lua>, TableValue)>);
 
 impl<'lua> std::fmt::Debug for TableKey<'lua> {
@@ -18,7 +18,7 @@ impl<'lua> std::fmt::Debug for TableKey<'lua> {
 
 impl std::fmt::Debug for TableValue {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.0)
+        f.write_str(&self.0)
     }
 }
 
diff --git a/src/Rust/vvs_utils/Cargo.toml b/src/Rust/vvs_utils/Cargo.toml
index 592c9a48..b27c119f 100644
--- a/src/Rust/vvs_utils/Cargo.toml
+++ b/src/Rust/vvs_utils/Cargo.toml
@@ -5,3 +5,8 @@ authors.workspace = true
 edition.workspace = true
 license.workspace = true
 description = "Utility crate for VVS"
+
+[dependencies]
+thiserror.workspace = true
+serde.workspace = true
+log.workspace = true
diff --git a/src/Rust/vvs_utils/src/lib.rs b/src/Rust/vvs_utils/src/lib.rs
index fad61c68..2130d45f 100644
--- a/src/Rust/vvs_utils/src/lib.rs
+++ b/src/Rust/vvs_utils/src/lib.rs
@@ -1,8 +1,12 @@
+#![forbid(unsafe_code)]
+
 mod angles;
 mod assert;
 mod conds;
 mod minmax;
 
+pub mod xdg;
+
 pub use angles::*;
 pub use assert::*;
 pub use conds::*;
diff --git a/src/Rust/vvs_utils/src/minmax.rs b/src/Rust/vvs_utils/src/minmax.rs
index 80252cee..70263bb9 100644
--- a/src/Rust/vvs_utils/src/minmax.rs
+++ b/src/Rust/vvs_utils/src/minmax.rs
@@ -1,19 +1,11 @@
 #[inline]
 pub fn min<T: PartialOrd>(a: T, b: T) -> T {
-    if a < b {
-        a
-    } else {
-        b
-    }
+    crate::either!(a < b => a; b)
 }
 
 #[inline]
 pub fn max<T: PartialOrd>(a: T, b: T) -> T {
-    if a > b {
-        a
-    } else {
-        b
-    }
+    crate::either!(a > b => a; b)
 }
 
 #[macro_export]
diff --git a/src/Rust/vvs_utils/src/xdg/config.rs b/src/Rust/vvs_utils/src/xdg/config.rs
new file mode 100644
index 00000000..3e15ff69
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/config.rs
@@ -0,0 +1,384 @@
+//! Utilities to extend the [XDGFolder] thing with config files: take into account the
+//! deserialization, merge of configs and others.
+
+use crate::xdg::{MaybeFolderList, XDGError, XDGFindBehaviour, XDGFolder};
+use serde::{Deserialize, Serialize};
+use std::rc::Rc;
+
+/// Search configurations in all config folders and merge them.
+#[derive(Debug)]
+pub struct XDGConfigMerged;
+
+/// Only get the config file with higher priority, which means searching in order:
+/// 1. $XDG_CONFIG_HOME <- which has a default value.
+/// 2. $XDG_CONFIG_DIRS <- which has a default value.
+#[derive(Debug)]
+pub struct XDGConfigFirst;
+
+/// Search configurations in all config folders and merge them. If the parsing for a configuration
+/// file failed then log it and continue execution like normal, i.e. fail silently.
+#[derive(Debug)]
+pub struct XDGConfigMergedSilent;
+
+pub type DeserializeFunctionPtr<'a, Format> =
+    Rc<dyn Fn(String) -> Result<Format, Box<dyn std::error::Error>> + 'static>;
+
+/// We will write one impl block for merged searches.
+mod private_merged {
+    use crate::xdg::*;
+    use serde::Deserialize;
+    use std::collections::VecDeque;
+
+    /// Search with merging.
+    pub trait Sealed: Sized {
+        fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError>
+        where
+            Format: for<'de> Deserialize<'de> + Extend<Format>;
+    }
+
+    impl Sealed for XDGConfigMergedSilent {
+        fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError>
+        where
+            Format: for<'de> Deserialize<'de> + Extend<Format>,
+        {
+            use XDGError::*;
+            let result = config.search_files()?.into_iter().filter_map(|file| {
+            let file = std::fs::read_to_string(file)
+                .map_err(|err| log::error!(target: "xdg", "{}", ConfigIO(config.app.to_string(), config.get_file().to_string(), err)))
+                .ok()?;
+            config.deserialize.as_ref()(file)
+                .map_err(|err| log::error!(target: "xdg", "{}", DeserializeError(config.app.to_string(), config.get_file().to_string(), err)))
+                .ok()
+        });
+
+            let mut result: VecDeque<Format> = result.collect();
+            let mut first = result
+                .pop_front()
+                .ok_or(ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?;
+            first.extend(result);
+            Ok(first)
+        }
+    }
+
+    impl Sealed for XDGConfigMerged {
+        fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError>
+        where
+            Format: for<'de> Deserialize<'de> + Extend<Format>,
+        {
+            use XDGError::*;
+            let mut result: VecDeque<Format> = Default::default();
+            for file in config.search_files()?.into_iter() {
+                let file = std::fs::read_to_string(file)
+                    .map_err(|err| ConfigIO(config.app.to_string(), config.get_file().to_string(), err))?;
+                let file = config.deserialize.as_ref()(file)
+                    .map_err(|err| DeserializeError(config.app.to_string(), config.get_file().to_string(), err))?;
+                result.push_back(file);
+            }
+            let mut first = result
+                .pop_front()
+                .ok_or(ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?;
+            first.extend(result);
+            Ok(first)
+        }
+    }
+}
+
+/// A struct to contain informations about the app we are querying the config for. The behaviour of
+/// the search can be changed.
+/// ---
+/// On the `Format` generic parameter:
+/// - If the `Format` parameter implements [Serialize], then you can write or provide a default
+///   that will be written to disk (or fail silently...)
+/// - If the `Format` parameter implements [Serialize] and [Default], you can depend on the
+///   default value if an error occured in the deserialization.
+/// ---
+/// On the `Merged` generic parameter:
+/// - If the `Merged` parameter is [XDGConfigFirst], then only the file with the higher priority
+///   is parsed.
+/// - If the `Merged` parameter is [XDGConfigMerged] and `Format` implements [Extend] on itself,
+///   then all the found files are parsed and then merged, an error is returned on the first
+///   failure of the deserialization process.
+pub struct XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de>,
+{
+    /// The application name.
+    app: &'a str,
+
+    /// The file name. If not present will be defaulted to `config` at resolution time.
+    file: Option<&'a str>,
+
+    /// Deserializer function.
+    deserialize: DeserializeFunctionPtr<'a, Format>,
+
+    /// Stores the format for deserialization.
+    _type: std::marker::PhantomData<Format>,
+
+    /// Stores the format for deserialization.
+    _merged: std::marker::PhantomData<Merged>,
+}
+
+impl<'a, Format, Merged> std::fmt::Debug for XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de>,
+{
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("XDGConfig")
+            .field("app", &self.app)
+            .field("file", &self.get_file())
+            .field("merged", &self._merged)
+            .finish()
+    }
+}
+
+impl<'a, Format, Merged> XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de>,
+{
+    /// By default we say that the default config file is ${FOLDER}/${APP}/config like many
+    /// applications do.
+    const DEFAULT_FILE: &'static str = "config";
+
+    /// Create a new [XDGConfig] helper.
+    pub fn new(
+        app: &'a str,
+        deserialize: impl Fn(String) -> Result<Format, Box<dyn std::error::Error>> + 'static,
+    ) -> Self {
+        Self {
+            app,
+            deserialize: Rc::new(deserialize),
+            file: Default::default(),
+            _type: Default::default(),
+            _merged: Default::default(),
+        }
+    }
+
+    /// Change the file to resolve.
+    pub fn file(&mut self, file: &'a str) -> &mut Self {
+        self.file = Some(file);
+        self
+    }
+
+    /// Get the name of the config file that we will try to read. Returns the default if not set by
+    /// the user.
+    pub fn get_file(&self) -> &str {
+        match self.file.as_ref() {
+            Some(file) => file,
+            None => Self::DEFAULT_FILE,
+        }
+    }
+
+    /// Search all config files in all locations...
+    fn search_files(&self) -> Result<MaybeFolderList, XDGError> {
+        XDGFolder::ConfigDirs.find(self.app, self.get_file(), XDGFindBehaviour::ExistingOnly)
+    }
+
+    /// Prepare config folders.
+    pub fn prepare_folder(&self) -> impl IntoIterator<Item = <MaybeFolderList as IntoIterator>::Item> {
+        XDGFolder::ConfigDirs.prepare_folder()
+    }
+}
+
+impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst>
+where
+    Format: for<'de> Deserialize<'de>,
+{
+    /// Try to read the config file and deserialize it.
+    pub fn try_read(&self) -> Result<Format, XDGError> {
+        let Some(file) = self.search_files()?.into_first() else {
+            return Err(XDGError::ConfigNotFound(
+                self.app.to_string(),
+                self.get_file().to_string()
+            ));
+        };
+        let file = std::fs::read_to_string(file)
+            .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err))?;
+        self.deserialize.as_ref()(file)
+            .map_err(|err| XDGError::DeserializeError(self.app.to_string(), self.get_file().to_string(), err))
+    }
+}
+
+impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst>
+where
+    Format: for<'de> Deserialize<'de> + Serialize,
+{
+    /// Try to read the config file and deserialize it. If an error is encountred at any point, log
+    /// it and return the provided default. If needed the default value is written on the disk,
+    /// note that this operation may fail silently.
+    pub fn read_or(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        default: Format,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.write_silent(serialize, default)
+        })
+    }
+
+    /// Try to read the config file and deserialize it. If an error is encountred at any point, log
+    /// it and return the provided default. If needed the default value is written on the disk,
+    /// note that this operation may fail silently.
+    pub fn read_or_else(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        cb: impl FnOnce() -> Format,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.write_silent(serialize, cb())
+        })
+    }
+
+    /// Write a value to the default config file location, the one with the higher priority
+    /// (usually the user's one)
+    fn write(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        value: &Format,
+    ) -> Result<(), XDGError> {
+        let content = serialize(value)
+            .map_err(|err| XDGError::SerializeError(self.app.to_string(), self.get_file().to_string(), err))?;
+        let Some(path) = XDGFolder::ConfigDirs
+            .find(self.app, self.get_file(), XDGFindBehaviour::FirstOrCreate)?
+            .into_first() else { unreachable!(
+                "the user must have at least one location to place a config file, permission or fs quota problem?"
+        )};
+
+        match std::fs::create_dir_all(path.parent().ok_or(XDGError::ConfigFileHasNoParentFolder(
+            self.app.to_string(),
+            self.get_file().to_string(),
+        ))?) {
+            Err(err) if !matches!(err.kind(), std::io::ErrorKind::AlreadyExists) => {
+                return Err(XDGError::ConfigIO(
+                    self.app.to_string(),
+                    self.get_file().to_string(),
+                    err,
+                ))
+            }
+            _ => {}
+        }
+
+        std::fs::write(path, content)
+            .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err))?;
+
+        Ok(())
+    }
+
+    /// Same as [XDGConfig::write] but log any error and fail silently, returning the value that
+    /// was attempted to be written to disk.
+    fn write_silent(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        value: Format,
+    ) -> Format {
+        if let Err(err) = self.write(serialize, &value) {
+            log::error!(target: "xdg", "failed to write default with err: {err}")
+        }
+        value
+    }
+}
+
+impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst>
+where
+    Format: for<'de> Deserialize<'de> + Serialize + Default,
+{
+    /// Try to read the config file and deserialize it. If an error is encountred at any point, log
+    /// it and return the default. If needed the default value is written on the disk, note that
+    /// this operation may fail silently.
+    pub fn read_or_default(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.write_silent(serialize, Default::default())
+        })
+    }
+}
+
+// XDGConfigMerged + XDGConfigMergedSilent
+
+impl<'a, Format, Merged> XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de> + Extend<Format>,
+    Merged: private_merged::Sealed,
+{
+    /// When trying to read or write the default, we write the file with the same logic as the
+    /// [XDGConfigFirst] variant, we add this function to reduce the code to write for the write
+    /// logic...
+    fn to_config_first(&self) -> XDGConfig<Format, XDGConfigFirst> {
+        XDGConfig::<Format, XDGConfigFirst> {
+            app: self.app,
+            file: self.file,
+            deserialize: self.deserialize.clone(),
+            _type: std::marker::PhantomData,
+            _merged: std::marker::PhantomData,
+        }
+    }
+
+    /// Try to read the config files and deserialize them. If one config file failed to be
+    /// deserialized then returns an error if the merged was not silent, or just ignore the file if
+    /// the merge was silent. If no config file where found this is an error. All the files are
+    /// merged, this operation can't fail, implementations must fail silently or have sane defaults
+    /// and prefer files with higher priority.
+    pub fn try_read(&self) -> Result<Format, XDGError> {
+        Merged::try_read(self)
+    }
+}
+
+impl<'a, Format, Merged> XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de> + Extend<Format> + Serialize,
+    Merged: private_merged::Sealed,
+{
+    /// Try to read the config files and deserialize them. If an error is encountred at any point,
+    /// log it and return the provided default if the merge was not silent, skip the file if it was
+    /// silent. If needed the default value is written on the disk, note that this operation may
+    /// fail silently.
+    pub fn read_or(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        default: Format,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.to_config_first().write_silent(serialize, default)
+        })
+    }
+
+    /// Try to read the config files and deserialize them. If an error is encountred at any point,
+    /// log it and return the provided default if the merge was not silent, skip the file if it was
+    /// silent. If needed the default value is written on the disk, note that this operation may
+    /// fail silently.
+    pub fn read_or_else(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+        cb: impl FnOnce() -> Format,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.to_config_first().write_silent(serialize, cb())
+        })
+    }
+}
+
+impl<'a, Format, Merged> XDGConfig<'a, Format, Merged>
+where
+    Format: for<'de> Deserialize<'de> + Extend<Format> + Serialize + Default,
+    Merged: private_merged::Sealed,
+{
+    /// Try to read the config files and deserialize them. If an error is encountred at any point,
+    /// log it and return the provided default if the merge was not silent, skip the file if it was
+    /// silent. If needed the default value is written on the disk, note that this operation may
+    /// fail silently.
+    pub fn read_or_default(
+        &self,
+        serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>,
+    ) -> Format {
+        self.try_read().unwrap_or_else(|err| {
+            log::error!(target: "xdg", "read error, return default value to user: {err}");
+            self.to_config_first().write_silent(serialize, Default::default())
+        })
+    }
+}
diff --git a/src/Rust/vvs_utils/src/xdg/folders.rs b/src/Rust/vvs_utils/src/xdg/folders.rs
new file mode 100644
index 00000000..c517830c
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/folders.rs
@@ -0,0 +1,256 @@
+//! The main provider for resolving files with the XDG specification + some utilities.
+
+use crate::{
+    either,
+    xdg::{MaybeFolderList, XDGError, XDGFindBehaviour},
+};
+use std::{io::ErrorKind as IoErrorKind, path::PathBuf};
+
+/// The type of folder we want. Here are some remarks from the specification about file resolution:
+///
+/// - If, when attempting to write a file, the destination directory is non-existent an attempt
+///   should be made to create it with permission 0700. If the destination directory exists already
+///   the permissions should not be changed. The application should be prepared to handle the case
+///   where the file could not be written, either because the directory was non-existent and could
+///   not be created, or for any other reason. In such case it may choose to present an error
+///   message to the user.
+/// - When attempting to read a file, if for any reason a file in a certain directory is
+///   unaccessible, e.g. because the directory is non-existent, the file is non-existent or the
+///   user is not authorized to open the file, then the processing of the file in that directory
+///   should be skipped. If due to this a required file could not be found at all, the application
+///   may choose to present an error message to the user.
+/// - A specification that refers to $XDG_DATA_DIRS or $XDG_CONFIG_DIRS should define what the
+///   behaviour must be when a file is located under multiple base directories. It could, for
+///   example, define that only the file under the most important base directory should be used or,
+///   as another example, it could define rules for merging the information from the different
+///   files.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum XDGFolder {
+    /// $XDG_DATA_HOME defines the base directory relative to which user-specific data files should
+    /// be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
+    /// $HOME/.local/share should be used.
+    DataHome,
+
+    /// $XDG_CONFIG_HOME defines the base directory relative to which user-specific configuration
+    /// files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to
+    /// $HOME/.config should be used.
+    ConfigHome,
+
+    /// $XDG_STATE_HOME defines the base directory relative to which user-specific state files
+    /// should be stored. If $XDG_STATE_HOME is either not set or empty, a default equal to
+    /// $HOME/.local/state should be used.
+    ///
+    /// The $XDG_STATE_HOME contains state data that should persist between (application) restarts,
+    /// but that is not important or portable enough to the user that it should be stored in
+    /// $XDG_DATA_HOME. It may contain:
+    /// - actions history (logs, history, recently used files, …)
+    /// - current state of the application that can be reused on a restart (view, layout, open
+    ///   files, undo history, …)
+    StateHome,
+
+    /// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data
+    /// files in addition to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS
+    /// should be seperated with a colon ':'.
+    ///
+    /// If $XDG_DATA_DIRS is either not set or empty, a value equal to
+    /// /usr/local/share/:/usr/share/ should be used.
+    DataDirs,
+
+    /// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for
+    /// configuration files in addition to the $XDG_CONFIG_HOME base directory. The directories in
+    /// $XDG_CONFIG_DIRS should be seperated with a colon ':'.
+    ///
+    /// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
+    ConfigDirs,
+
+    /// $XDG_CACHE_HOME defines the base directory relative to which user-specific non-essential
+    /// data files should be stored. If $XDG_CACHE_HOME is either not set or empty, a default equal
+    /// to $HOME/.cache should be used.
+    CacheHome,
+
+    /// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential
+    /// runtime files and other file objects (such as sockets, named pipes, ...) should be stored.
+    /// The directory MUST be owned by the user, and he MUST be the only one having read and write
+    /// access to it. Its Unix access mode MUST be 0700.
+    ///
+    /// The lifetime of the directory MUST be bound to the user being logged in. It MUST be created
+    /// when the user first logs in and if the user fully logs out the directory MUST be removed.
+    /// If the user logs in more than once he should get pointed to the same directory, and it is
+    /// mandatory that the directory continues to exist from his first login to his last logout on
+    /// the system, and not removed in between. Files in the directory MUST not survive reboot or a
+    /// full logout/login cycle.
+    ///
+    /// The directory MUST be on a local file system and not shared with any other system. The
+    /// directory MUST by fully-featured by the standards of the operating system. More
+    /// specifically, on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links,
+    /// proper permissions, file locking, sparse files, memory mapping, file change notifications,
+    /// a reliable hard link count must be supported, and no restrictions on the file name
+    /// character set should be imposed. Files in this directory MAY be subjected to periodic
+    /// clean-up. To ensure that your files are not removed, they should have their access time
+    /// timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should
+    /// be set on the file.
+    ///
+    /// If $XDG_RUNTIME_DIR is not set applications should fall back to a replacement directory
+    /// with similar capabilities and print a warning message. Applications should use this
+    /// directory for communication and synchronization purposes and should not place larger files
+    /// in it, since it might reside in runtime memory and cannot necessarily be swapped out to
+    /// disk.
+    RuntimeDir,
+}
+
+impl XDGFolder {
+    /// The list separator.
+    const SEPARATOR: char = ':';
+
+    /// Get the folders or folder list that the user asked for. If the env variable is not present,
+    /// falls back to the default value.
+    ///
+    /// Note that the folder or folders doesn't exist, this function won't try to create them.
+    pub fn get_folder(&self) -> MaybeFolderList {
+        use XDGFolder::*;
+        let home = crate::xdg::home_folder();
+        match std::env::var(self.env_var_name()) {
+            Ok(folder_list) if self.is_list() => MaybeFolderList::from_str_list(&folder_list, Self::SEPARATOR),
+            Ok(folder) => MaybeFolderList::Folder(PathBuf::from(folder)),
+            Err(_) => match self {
+                DataHome => MaybeFolderList::Folder(home.join(".local/share")),
+                ConfigHome => MaybeFolderList::Folder(home.join(".config")),
+                StateHome => MaybeFolderList::Folder(home.join(".local/state")),
+                CacheHome => MaybeFolderList::Folder(home.join(".cache")),
+                RuntimeDir => panic!("failed to find the env variable $XDG_RUNTIME_DIR"),
+                DataDirs => MaybeFolderList::from_iter(["/usr/local/share", "/usr/share"]),
+                ConfigDirs => MaybeFolderList::from("/etc/xdg"),
+            },
+        }
+    }
+
+    /// Get the folders list of what you asked for, but try to create them and only return existing
+    /// ones. The thing returned is a thing that can be iterate over to facilitate chaining.
+    pub fn prepare_folder(&self) -> impl IntoIterator<Item = <MaybeFolderList as IntoIterator>::Item> {
+        self.get_folder().into_iter().filter_map(|folder| {
+            if folder.is_dir() {
+                Some(folder)
+            } else {
+                std::fs::create_dir_all(&folder)
+                    .map(|()| folder)
+                    .map_err(|err| log::error!(target: "xdg", "{err}"))
+                    .ok()
+            }
+        })
+    }
+
+    /// Get the file associated with the said application in the specified folders. An option can
+    /// be passed to specify behaviour in the search or file/folder creation processes.
+    pub fn find(
+        &self,
+        app: impl AsRef<str>,
+        file: impl AsRef<str>,
+        opt: XDGFindBehaviour,
+    ) -> Result<MaybeFolderList, XDGError> {
+        enum FindState {
+            CanCreate(PathBuf),
+            Exists(PathBuf),
+        }
+        impl<'a> std::iter::Extend<&'a FindState> for MaybeFolderList {
+            fn extend<T: IntoIterator<Item = &'a FindState>>(&mut self, iter: T) {
+                self.extend(iter.into_iter().map(|m| match m {
+                    FindState::CanCreate(path) | FindState::Exists(path) => path.clone(),
+                }));
+            }
+        }
+
+        // DataHome is more important than DataDirs in the search process... Idem for the Config
+        // env var family...
+        let matches: Vec<_> = match self {
+            XDGFolder::DataDirs => XDGFolder::DataHome.get_folder(),
+            XDGFolder::ConfigDirs => XDGFolder::ConfigHome.get_folder(),
+            _ => MaybeFolderList::Empty,
+        }
+        .into_iter()
+        .chain(self.get_folder().into_iter())
+        .flat_map(|path| {
+            let path = path.join(app.as_ref());
+            if let Err(err) = std::fs::create_dir_all(&path) {
+                if !matches!(err.kind(), IoErrorKind::PermissionDenied | IoErrorKind::AlreadyExists) {
+                    log::error!(target: "xdg",
+                        "failed to create app folder for {{{self}}}/{}/{} as {}",
+                        app.as_ref(), file.as_ref(), path.to_string_lossy()
+                    );
+                    return None;
+                }
+            }
+            let path = path.join(file.as_ref());
+            Some(either!(path.exists() => FindState::Exists(path); FindState::CanCreate(path)))
+        })
+        .collect();
+
+        // For now, default behaviour is to return the first file found or try to create it, the
+        // first file created will be returned (if we can't create the file in system folders it's
+        // not an error...)
+
+        let (mut exists, mut can_create): (MaybeFolderList, MaybeFolderList) =
+            matches.iter().partition(|m| matches!(m, FindState::Exists(_)));
+        use XDGFindBehaviour::*;
+        match (exists.is_some(), can_create.is_some()) {
+            // [XDGFindBehaviour::FirstOrCreate] case...
+            (true, false) if opt == FirstOrCreate => Ok(exists.into_first().into_iter().collect()),
+            (false, true) if opt == FirstOrCreate => Ok(can_create.into_first().into_iter().collect()),
+
+            // Should we return the one list that is not empty?
+            (true, false) if matches!(opt, AllFiles | ExistingOnly) => Ok(exists),
+            (false, true) if matches!(opt, AllFiles | NonExistingOnly) => Ok(can_create),
+
+            // Complicated case
+            (true, true) => match opt {
+                AllFiles => {
+                    exists.append(&mut can_create);
+                    Ok(exists)
+                }
+
+                FirstOrCreate => Ok(exists.into_first().into_iter().collect()),
+                ExistingOnly => Ok(exists),
+                NonExistingOnly => Ok(can_create),
+            },
+
+            // The list that we want is empty, or all lists are empty, returns nothing -> should it
+            // really be an error?
+            _ => Err(XDGError::NotFound(
+                *self,
+                app.as_ref().to_string(),
+                file.as_ref().to_string(),
+            )),
+        }
+    }
+
+    /// Is this variable a folder list?
+    pub fn is_list(&self) -> bool {
+        use XDGFolder::*;
+        matches!(self, DataDirs | ConfigDirs)
+    }
+
+    /// Get the env variable name associated with the folder or folder list.
+    pub fn env_var_name(&self) -> &str {
+        self.as_ref()
+    }
+}
+
+impl AsRef<str> for XDGFolder {
+    fn as_ref(&self) -> &str {
+        match self {
+            XDGFolder::DataHome => "XDG_DATA_HOME",
+            XDGFolder::StateHome => "XDG_STATE_HOME",
+            XDGFolder::CacheHome => "XDG_CACHE_HOME",
+            XDGFolder::ConfigHome => "XDG_CONFIG_HOME",
+
+            XDGFolder::DataDirs => "XDG_DATA_DIRS",
+            XDGFolder::ConfigDirs => "XDG_CONFIG_DIRS",
+            XDGFolder::RuntimeDir => "XDG_RUNTIME_DIR",
+        }
+    }
+}
+
+impl std::fmt::Display for XDGFolder {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "${}", self.as_ref())
+    }
+}
diff --git a/src/Rust/vvs_utils/src/xdg/mod.rs b/src/Rust/vvs_utils/src/xdg/mod.rs
new file mode 100644
index 00000000..bc1e04b2
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/mod.rs
@@ -0,0 +1,47 @@
+//! Utility functions to follow the freedesktop specifications on config folders and such:
+//! https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+#[cfg(test)]
+mod tests;
+
+mod config;
+mod folders;
+mod options;
+mod paths;
+
+pub use config::*;
+pub use folders::*;
+pub use options::*;
+pub use paths::*;
+
+use std::{io::Error as IoError, path::PathBuf};
+use thiserror::Error;
+
+/// Get the user's home folder. Panics if the env variable was not found or it can't be
+/// canonicalized.
+pub fn home_folder() -> PathBuf {
+    PathBuf::from(std::env::var("HOME").expect("failed to get the $HOME env variable"))
+        .canonicalize()
+        .expect("failed to canonicalize the $HOME folder path")
+}
+
+#[derive(Debug, Error)]
+pub enum XDGError {
+    #[error("failed to find file {2} for application {1} with folder family {0}")]
+    NotFound(XDGFolder, String, String),
+
+    #[error("failed to find config file {1} for application {0}")]
+    ConfigNotFound(String, String),
+
+    #[error("failed to find parent folder of config file {1} for application {0}")]
+    ConfigFileHasNoParentFolder(String, String),
+
+    #[error("io error for config file {1} for application {0}: {2}")]
+    ConfigIO(String, String, IoError),
+
+    #[error("deserialization failed on file {1} for application {0} with: {2}")]
+    DeserializeError(String, String, Box<dyn std::error::Error>),
+
+    #[error("serialization failed on file {1} for application {0} with: {2}")]
+    SerializeError(String, String, Box<dyn std::error::Error>),
+}
diff --git a/src/Rust/vvs_utils/src/xdg/options.rs b/src/Rust/vvs_utils/src/xdg/options.rs
new file mode 100644
index 00000000..3985de17
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/options.rs
@@ -0,0 +1,24 @@
+//! Options that can be passed to some functions to control the behaviour.
+
+/// Control the behaviour of the [XDGFolder::find] function on conflicts.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum XDGFindBehaviour {
+    /// Returns only the files that already exists. If no file is found, returns the file with the
+    /// post priority that can be created, usually where the user want this file to exists. If you
+    /// don't want to merge config/data files/folders this can be a sane default.
+    #[default]
+    FirstOrCreate,
+
+    /// Returns only the files that already exists. If you want to merge config/data files/folders
+    /// this can be a sane default.
+    ExistingOnly,
+
+    /// Only returns files that don't already exists and may be created. Note that even if a file
+    /// can be created by the user, it won't necessarily means that you will be able to, keep in
+    /// mind that race conditions may occur here.
+    NonExistingOnly,
+
+    /// Returns all files, the ones that exist and the ones that may be created. The user will need
+    /// to handle the returned files as he sees fit.
+    AllFiles,
+}
diff --git a/src/Rust/vvs_utils/src/xdg/paths.rs b/src/Rust/vvs_utils/src/xdg/paths.rs
new file mode 100644
index 00000000..d7982949
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/paths.rs
@@ -0,0 +1,208 @@
+//! A PathBuf container as a sum type.
+
+use std::path::{Path, PathBuf};
+
+/// An enum to store maybe multiple folders, or none, with utility functions.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub enum MaybeFolderList {
+    /// No folder.
+    #[default]
+    Empty,
+
+    /// Only one folder.
+    Folder(PathBuf),
+
+    /// Many folders.
+    Many(PathBuf, Vec<PathBuf>),
+}
+
+/// An interator over [MaybeFolderList].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MaybeFolderListIterator<'a> {
+    next: Option<&'a PathBuf>,
+    list: &'a [PathBuf],
+}
+
+impl MaybeFolderList {
+    /// Is there no folder?
+    pub fn is_none(&self) -> bool {
+        matches!(self, MaybeFolderList::Empty)
+    }
+
+    /// Is there folders?
+    pub fn is_some(&self) -> bool {
+        !self.is_none()
+    }
+
+    /// Get the first folder, or the last folder. If no folder is present just returns [None].
+    pub fn first(&self) -> Option<&Path> {
+        use MaybeFolderList::*;
+        match self {
+            Empty => None,
+            Folder(f) | Many(f, _) => Some(f.as_ref()),
+        }
+    }
+
+    /// Append another [MaybeFolderList] at the end of a [MaybeFolderList]. Empties the other
+    /// variable and try to reuse memory.
+    pub fn append(&mut self, other: &mut MaybeFolderList) {
+        use MaybeFolderList::*;
+
+        match (self, other) {
+            (this @ Empty, other) => match other {
+                Empty => {}
+                Folder(f1) => {
+                    *this = Folder(f1.clone());
+                    *other = Empty;
+                }
+                Many(f1, fs) => {
+                    *this = Many(f1.clone(), fs.drain(..).collect());
+                    *other = Empty;
+                }
+            },
+
+            (_, Empty) => {}
+
+            (Many(_, fs), other @ Folder(_)) => {
+                let Folder(f2) = other else { unreachable!() };
+                fs.push(f2.to_path_buf());
+                *other = Empty;
+            }
+            (Many(_, fs), other @ Many(_, _)) => {
+                let Many(f2, ref mut fs2) = other else { unreachable!() };
+                fs.reserve(fs2.len() + 1);
+                fs.push(f2.clone());
+                fs.append(fs2);
+                *other = Empty;
+            }
+
+            (this @ Folder(_), other) => {
+                let Folder(f1) = this else { unreachable!() };
+                match other {
+                    Empty => unreachable!(),
+                    Folder(f2) => *this = Many(f1.clone(), vec![f2.clone()]),
+                    Many(f2, ref mut fs) => {
+                        fs.insert(0, f2.clone());
+                        *this = Many(f1.clone(), fs.drain(..).collect());
+                    }
+                }
+                *other = Empty;
+            }
+        }
+    }
+
+    /// Get the first folder, or the last folder. If no folder is present just returns [None]. The
+    /// difference from the [MaybeFolderList::first] function is that this function consumes the
+    /// [MaybeFolderList].
+    pub fn into_first(self) -> Option<PathBuf> {
+        use MaybeFolderList::*;
+        match self {
+            Empty => None,
+            Folder(f) | Many(f, _) => Some(f),
+        }
+    }
+
+    /// Get an iterator over the contained folders.
+    pub fn iter(&self) -> MaybeFolderListIterator {
+        let (next, list): (_, &[_]) = match self {
+            MaybeFolderList::Empty => (None, &[]),
+            MaybeFolderList::Folder(f) => (Some(f), &[]),
+            MaybeFolderList::Many(f, fs) => (Some(f), &fs[..]),
+        };
+        MaybeFolderListIterator { next, list }
+    }
+
+    /// Create a list of folders from a description string and a separator.
+    pub(super) fn from_str_list(list: &str, separator: char) -> Self {
+        let mut folders = list.split(separator);
+        match folders.next() {
+            None => MaybeFolderList::Empty,
+            Some(first) => MaybeFolderList::Many(PathBuf::from(first), folders.map(PathBuf::from).collect()),
+        }
+    }
+}
+
+impl std::iter::Extend<PathBuf> for MaybeFolderList {
+    fn extend<T: IntoIterator<Item = PathBuf>>(&mut self, iter: T) {
+        use MaybeFolderList::*;
+        let mut iter = iter.into_iter();
+        match self {
+            Empty => {
+                if let Some(next) = iter.next() {
+                    *self = Many(next, iter.collect());
+                }
+            }
+            Folder(f1) => *self = Many(f1.clone(), iter.collect()),
+            Many(_, fs) => fs.extend(iter),
+        }
+    }
+}
+
+impl<'a> std::iter::Extend<&'a MaybeFolderList> for MaybeFolderList {
+    fn extend<T: IntoIterator<Item = &'a MaybeFolderList>>(&mut self, iter: T) {
+        use MaybeFolderList::*;
+        let other = iter.into_iter().flat_map(|iter| iter.iter());
+        match self {
+            Empty => *self = other.collect(),
+            Folder(f1) => *self = Many(f1.clone(), other.cloned().collect()),
+            Many(_, ref mut fs) => fs.extend(other.cloned()),
+        }
+    }
+}
+
+impl<'a, S> From<S> for MaybeFolderList
+where
+    S: AsRef<str> + 'a,
+{
+    fn from(value: S) -> Self {
+        MaybeFolderList::Folder(PathBuf::from(value.as_ref()))
+    }
+}
+
+impl<'a, P> FromIterator<P> for MaybeFolderList
+where
+    P: AsRef<Path> + 'a,
+{
+    fn from_iter<T: IntoIterator<Item = P>>(iter: T) -> Self {
+        let mut folders = iter.into_iter();
+        match folders.next() {
+            None => MaybeFolderList::Empty,
+            Some(first) => MaybeFolderList::Many(
+                first.as_ref().to_path_buf(),
+                folders.map(|p| p.as_ref().to_path_buf()).collect(),
+            ),
+        }
+    }
+}
+
+impl IntoIterator for MaybeFolderList {
+    type Item = PathBuf;
+    type IntoIter = <Vec<PathBuf> as IntoIterator>::IntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        match self {
+            MaybeFolderList::Empty => vec![].into_iter(),
+            MaybeFolderList::Folder(f) => vec![f].into_iter(),
+            MaybeFolderList::Many(f, mut fs) => {
+                fs.insert(0, f);
+                fs.into_iter()
+            }
+        }
+    }
+}
+
+impl<'a> Iterator for MaybeFolderListIterator<'a> {
+    type Item = &'a PathBuf;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let next = self.next?;
+        match self.list.split_first() {
+            Some((next, list)) => {
+                self.next = Some(next);
+                self.list = list;
+            }
+            None => self.next = None,
+        }
+        Some(next)
+    }
+}
diff --git a/src/Rust/vvs_utils/src/xdg/tests.rs b/src/Rust/vvs_utils/src/xdg/tests.rs
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/src/Rust/vvs_utils/src/xdg/tests.rs
@@ -0,0 +1 @@
+
diff --git a/utils/vvs/retime.vvl b/utils/vvs/retime.vvl
index 8ad09905..79f6c8be 100644
--- a/utils/vvs/retime.vvl
+++ b/utils/vvs/retime.vvl
@@ -10,6 +10,14 @@ option "after" { type = "number", default = 300 }
 -- `line.aux.number` here. Note that name collision will raise an error...
 data "line:number" { type = "number", default = 0 }
 
+-- You may declare functions like that, this is the only way of making them
+-- visible to the exterior of the module and even inside the module
+func "my_function" {
+    function()
+        print "Ok"
+    end
+}
+
 -- To declare a job we must give it a name. The function to use must follow
 -- some rules. It must only have one argument, the item on which it must be
 -- called (line, syllabe, lines, syllabes). The function must returns one type
@@ -21,7 +29,7 @@ job "preline" {
         local pre_line = line:copy()
         pre_line.start = line.start - before
         pre_line.fini  = line.start
-        for syl in pre_line:syllabes() do
+        for _, syl in ipairs(pre_line) do
             syl.start = syl.start - before
             syl.fini  = line.start
         end
@@ -34,7 +42,7 @@ job "postline" {
         local post_line = line:copy()
         post_line.start = line.fini
         post_line.fini  = line.fini + after
-        for syl in post_line:syllabes() do
+        for _, syl in ipairs(post_line) do
             syl.start = line.fini
             syl.fini  = syl.fini + after
         end
@@ -56,3 +64,14 @@ job "countlines" {
         return lines
     end
 }
+
+-- Dummy job here to expose the way of calling internally defined functions,
+-- it's like calling a function from another module, but with "self". The "self"
+-- table will automatically swapped to the correct one when calling a job from
+-- different modules.
+job "dummy-job" {
+    lines = function(lines)
+        self.my_function() -- Call the previously defined "my_function"
+        return lines
+    end
+}
diff --git a/utils/vvs/test2.vvs b/utils/vvs/test2.vvs
index 35ed801e..c9f7f552 100644
--- a/utils/vvs/test2.vvs
+++ b/utils/vvs/test2.vvs
@@ -24,5 +24,6 @@ job "ziplines" {
 main {
     prelines  = retime.preline "INIT",
     postlines = retime.postline "INIT",
+    -- Left jobs and functions are exported as the "self" module when calling the main block.
     self.ziplines { "prelines", "INIT", "postlines" },
 }
-- 
GitLab