ratslang 0.1.0

Simple configuration language for working with physical systems.
Documentation

Ratslang is a compact configuration language, delivered as a Rust library.

It was born out of frustration with the lack of proper types for time and length in configuration files. When we configure physical systems like robots, even a single zero more or less can have massive consequences. Sometimes, we don't even realize there's a problem until it's too late. The famous Loss of the Mars Climate Orbiter is a prime example of this issue.

The core motivations behind Ratslang are:

  • Solving units: The language should inherently handle units when configuring physical systems.
  • Combining configs: It should be easier to combine existing configuration files rather than copying them.
  • Simple and extensible: The implementation should be small, simple, and easy to extend.

Let's take a look at how it works:

# a comment
variable = true

time = 1s
time_is_running = 1ms..2mins # ranges convert automatically

len = 1cm..1m

# _ signals a variable the interpreter is looking for.
_internal = time_is_running

my.super.long.prefix.var = 0..100 # ranges on namespaced variable "var"

# nested
my.super {

    long.prefix {
        next_var = "UTF-🎱 Strings"
    }

    something_else = -99.018
}

mat = [ [ 6, 1, 9 ],
        [ 3, 1, 8 ] ]

Currently, Ratslang doesn't support expressions like arithmetic, loops, or conditional statements. This is a deliberate choice, and it's still undecided if it will ever include such features. Some keywords are already reserved for potential future use.


Variables

  • Dynamic types
  • Mutable
  • Copy-on-assignment
  • C-style naming

Types

  • Boolean: true, false
  • Integer: Example: 42, -100
  • Floating-point: Example: 69.42, -3.14
  • String: Quotes can be omitted if the string doesn't conflict with a previously defined variable. Example: "my string", another_string_without_quotes
  • Path: Example: ./somewhere/relative.dat, /or/absolute, ./../backneed/dotfirst.launch.py
  • Array/Matrix: Newlines after commas are also supported for readability. [ <Type>, <Type>, ... ], [ 42, World, [ "nested" ] ], [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
  • Time:
    • Hour: hour, hours (s is optional). Example: 2hours, 1.5hour
    • Minute: min, mins (s is optional). Example: 30min, 5mins
    • Second: s. Example: 10s, 0.5s
    • Millisecond: ms. Example: 200ms, 1ms
  • Length:
    • Meter: m. Example: 10m, 0.5m
    • Centimeter: cm. Example: 50cm, 2.5cm
    • Millimeter: mm. Example: 100mm, 1mm
  • Range: Including unbound variants and empty ...
    • Time: Example: 1ms..5.3hours, 6s..
    • Length: Example: 1mm..100m, ..4m
    • Numbers: Example: -4..l, 6.00001..6.0001

Includes

In Ratslang, including is done by assigning a path to the current namespace. All variables will then get the respective prefix.

= ./path_relative_to_current_file.rl

strangefile {
  = ./../namespacing_contents.rl
}

Library Usage

Add this to your Cargo.toml.

ratslang = { version = "0.1.0-alpha.3", git = "https://github.com/stelzo/ratslang", branch = "main" }

First, you compile a Ratslang file to get a cleaned Abstract Syntax Tree (AST) with all variables resolved.

let file = std::path::Path::new("./your_file.rl");
let ast = ratslang::compile_file(&file.to_path_buf(), None, None).unwrap();

Then, you can safely read the variables you need — either with the provided helper macros for concise code, or manually using Rust's powerful pattern matching.

Using helper macros (recommended):

use ratslang::{
    compile_file,
    resolve_string, resolve_bool, resolve_int, resolve_float,
    resolve_int_range, resolve_length_range_meters_float, resolve_time_range_seconds_float,
    resolve_path,
};

// Local configs combining user vars and optional defaults
struct Configs {
    user: ratslang::VariableHistory,
    defaults: ratslang::VariableHistory,
}

let file = std::path::Path::new("./your_file.rl");
let ast = compile_file(&file.to_path_buf(), None, None).unwrap();
let configs = Configs { user: ast.vars, defaults: ratslang::VariableHistory::new(vec![]) };

// Simple values
let name: String = resolve_string!(configs, "name")?;
let enabled: bool = resolve_bool!(configs, "variable")?;
let k: i64 = resolve_int!(configs, "k_neighbors")?;
let ratio: f64 = resolve_float!(configs, "something_else")?;

// Paths
let path: String = resolve_path!(configs, "_file")?; // e.g., `_file = /abs/or/relative`

// Ranges (with sensible defaults used when bounds are missing)
let (d_min, d_max) = resolve_length_range_meters_float!(configs, "len", 0.0, 10.0)?; // meters as f64
let (t_min, t_max) = resolve_time_range_seconds_float!(configs, "time_is_running", 0.0, 60.0)?; // seconds as f64
let (i_min, i_max) = resolve_int_range!(configs, "my.super.long.prefix.var", 0, 100)?;

Manual resolution:

use anyhow::anyhow;
use ratslang::{compile_file, Rhs, Val, NumVal};

// Local configs combining user vars and optional defaults
struct Configs {
    user: ratslang::VariableHistory,
    defaults: ratslang::VariableHistory,
}

let file = std::path::Path::new("./your_file.rl");
let ast = compile_file(&file.to_path_buf(), None, None).unwrap();
let vars = ast.vars.filter_ns(&["_my_namespace"]);

// Resolve a variable and pattern-match its type manually
let value = vars
    .resolve("_some_var")?
    .map_or(Ok("a_default_value".to_owned()), |rhs| {
        Ok(match rhs {
            Rhs::Val(Val::StringVal(s)) => s,
            _ => {
                return Err(anyhow!(
                    "Unexpected type for _my_namespace._some_var, expected String."
                ));
            }
        })
    })?;

// Or use the generic resolve_var! macro
use ratslang::resolve_var;
let configs = Configs { user: vars, defaults: ratslang::VariableHistory::new(vec![]) };
let k_neighbors: usize = resolve_var!(configs, k_neighborhood, as usize,
    Rhs::Val(Val::NumVal(NumVal::Integer(i))) => { i }
)?;

  • Ratslang files typically use the .rl extension.
  • Syntax highlighting is available with this tree-sitter grammar or this VS Code extension. For Markdown files, you can use the awk language for syntax highlighting. It is not perfect but works reasonably well.
  • Compile errors are beautifully rendered thanks to Ariadne ❤️.

Ratslang: More a slang, less a lang.


Future Plans

The following features and improvements are planned:

  • Expanded Units and Scales: Integrate more diverse units and scales with centralized conversion, ranging from astronomy to quantum physics, powered by the uom crate.
  • Opt-in Language Versioning: Implement an opt-in versioning system for .rl files.

License