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.
- Type-safe units: Physical values are stored using the
uomcrate with full precision - no rounding.
Let's take a look at how it works:
# a comment
variable = true
time = 1s
time_is_running = 1ms..2min # ranges convert automatically
# nanometers and micrometers for precision work
precision_length = 500nm..10um
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 (stored as
uom::si::f64::Time):- Second with SI prefixes:
ysthroughYs(e.g.,10s,0.5s,1ms,2ks) - Hour:
h,hour,hours. Example:2h,1.5hour - Minute:
min,minute,minutes. Example:30min,5minutes - Year:
a,year,years. Example:1a,2.5years - Day:
d,day,days. Example:7d,1.5days - Shake:
shake,shakes. Example:10shake - Sidereal variants (astronomical):
second_sidereal,hour_sidereal,day_sidereal,year_sidereal. Example:1day_sidereal,23.5hours_sidereal - Tropical year:
year_tropical,years_tropical. Example:1year_tropical
- Second with SI prefixes:
- Length (stored as
uom::si::f64::Length):- Meter with SI prefixes:
ymthroughYm(e.g.,10m,0.5m,1mm,2km) - Imperial/US:
ft/foot,in/inch,mi/mile,yd/yard. Example:5ft,12inches,3.5miles - Other common:
ch/chain,rd/rod,fathom. Example:1fathom,2chains - Astronomical:
ua/astronomical_unit,light_year,parsec(orl.y.,pc). Example:1.5au,4.37light_years - Nautical:
M/nautical_mile. Example:10nautical_miles - Small units:
angstrom(orÅ),micron(orµ),mil,microinch. Example:10angstrom,0.5microns - Atomic:
bohr_radius(ora₀),fermi. Example:1bohr_radius
- Meter with SI prefixes:
- Range: Including unbound variants and empty
...- Time: Example:
1ms..5.3h,6s..,30min..2d - Length: Example:
1mm..100m,..4m - Numbers: Example:
-4..l,6.00001..6.0001
- Time: Example:
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
}
Annotations
Annotations allow you to mark sections of your configuration for selective extraction and filtering. This is particularly useful for generating minimal configurations, feature-specific settings, or environment-dependent parameters.
Basic Usage
Annotate a single line with # @annotation_name:
# @minimal
timeout = 5000
# @dev
debug_enabled = true
production_only = false
Annotate an entire namespace block:
# @minimal
sensor {
type = Lidar
range = 100m
}
sensor.calibration_file = /path/to/calibration.dat
Extracting Annotations
Use to_string_filtered() to extract only annotated sections:
use compile_code;
let source = r#"
# @minimal
timeout = 5000
# @minimal
sensor {
type = Lidar
}
sensor.range = 100m
"#;
let ast = compile_code.unwrap;
let minimal_config = ast.to_string_filtered.unwrap;
// Result: only timeout and sensor block
Use Cases
- Minimal configurations: Use
@minimalto generate deployment-ready configs with only essential parameters - Feature flags: Use
@feature_nameto extract configurations for specific features - Environment-specific settings: Use
@prod,@dev,@testfor environment variants - Documentation examples: Use
@documentedto generate user-facing configuration examples
Library Usage
Add this to your Cargo.toml.
[]
= "0.4.1"
First, you compile a Ratslang file to get a cleaned Abstract Syntax Tree (AST) with all variables resolved.
let file = new;
let ast = compile_file.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 ;
// Local configs combining user vars and optional defaults
let file = new;
let ast = compile_file.unwrap;
let configs = Configs ;
// Simple values
let name: String = resolve_string!?;
let enabled: bool = resolve_bool!?;
let k: i64 = resolve_int!?;
let ratio: f64 = resolve_float!?;
// Paths
let path: String = resolve_path!?; // e.g., `_file = /abs/or/relative`
// Ranges (with sensible defaults used when bounds are missing)
let = resolve_length_range_meters_float!?; // meters as f64
let = resolve_time_range_seconds_float!?; // seconds as f64
let = resolve_int_range!?;
Working with uom types directly:
Physical values in Ratslang are stored as uom types (Length and Time), giving you full access to type-safe unit conversions:
use ;
use ; // Length units
use ; // Time units
let code = r#"
sensor_range = 500um
timeout = 1500ns
"#;
let ast = compile_code.unwrap;
// Get a length value and convert to any unit
if let Some = ast.vars.resolve.unwrap
// Get a time value
if let Some = ast.vars.resolve.unwrap
Manual resolution:
use anyhow;
use ;
// Local configs combining user vars and optional defaults
let file = new;
let ast = compile_file.unwrap;
let vars = ast.vars.filter_ns;
// Resolve a variable and pattern-match its type manually
let value = vars
.resolve?
.map_or?;
// Or use the generic resolve_var! macro
use resolve_var;
let configs = Configs ;
let k_neighbors: usize = resolve_var!?;
- Ratslang files typically use the
.rlextension. - Syntax highlighting is available with this tree-sitter grammar or this VS Code extension. For Markdown files, you can use the
awklanguage 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: Add more unit types (mass, angle, temperature, etc.) powered by the
uomcrate. - Opt-in Language Versioning: Implement an opt-in versioning system for
.rlfiles.