RSML as a Language¶
Abstract¶
Red Sea Markup Language (RSML) is a simple declarative markup language created for the purpose of following logic paths based on the host's operating system and CPU architecture.
It is a better altenative to scripting languages because of its simplicity, ease of use, and the fact it's not necessary to package a whole interpreter.
Evaluation¶
The "evaluation" is the act of going through every line of RSML and evaluating the ones that match the logic path syntax, while running the ones that match the special action syntax.
If an evaluation encounters the return operator (see Operators) in a logic path that evaluates as true, its value is returned and evaluation ends there.
Language Specification¶
Note
Starting with version 2.0.0, RSML is a standardized language, in order to ensure consistency across different implementations. This also allows for less confusion when it comes to the language's features and syntax.
Below are the main concepts of RSML as a language.
- Logic Path: The interactive part of RSML, responsible for returning values based on machine matches.
- Special Action: Actions that are executed during evaluation, responsible for modifying real-time aspects of how RSML is evaluated. There are only 3 as of now.
- Comments: Lines that are ignored by the parser.
Logic Path¶
A logic path is a line in RSML that contains several expressions, an operator and a value to return. If the expression matches the host's OS/architecture, the evaluator returns the value (if the operator is the return operator) or executes the operator's functionality.
Operators¶
In RSML, there are two operators, named return and throw-error.
Below is a table with the operators, their tokens and what they actually do.
| Operator Name | Operator Token | Functionality (triggered if the logic path matches the machine) |
|---|---|---|
| Return | -> |
Returns the logic path's value and ends evaluation. |
| Throw-Error | !> |
Throws an error (error message set to the logic path's value) and ends evaluation. |
Syntax¶
The syntax for logic paths is extremely simple. It has multiple overloads, depending on how many parameters you want to specify. Order must be followed as shown below, for the overload you want to use.
Parameters¶
The parameters in the syntax, although complex at first glance, are quite simple. However, they must appear in the exact order shown above (depending on the overload used).
operator-
The operator to use. This is mandatory.
Supported Operators:
- 2.0.0-prerelease8
->(return operator) - 2.0.0-prerelease8
!>(throw-error operator)
- 2.0.0-prerelease8
system-name2.0.0-prerelease8-
The operating system name to match against. This is optional; but must be specified if you wish to use any overload that includes it. Can be set to
anyto match all operating systems as well. Can, additionally, be set todefinedto match all operating systems as long as the OS is recognized by the RSML implementation.Supported Systems:
- 2.0.0-prerelease8
windows - 2.0.0-prerelease8
linux(matches all Linux distributions, even if the specific distro is not recognized) - 2.0.0-prerelease8
osx(MacCatalyst is untested but should work) - 2.0.0-prerelease8
freebsd(OpenBSD and NetBSD are probably considered different OSes and thus not matched - that is up to .NET's implementation) - 2.0.0-prerelease8
debian - 2.0.0-prerelease8
ubuntu - 2.0.0-prerelease8
archlinux(calledarchlinuxinstead of justarchto avoid confusion with CPU _arch_itectures) - 2.0.0-prerelease8
fedora - More OSes may be added in future versions... (for now, any other value will throw a lexer error)
Allowed Wildcards:
- 2.0.0-prerelease8
any(not an OS, but matches all OSes) - 2.0.0-prerelease8
defined(not an OS, but matches all OSes, as long as there's information about them available to RSML)
How OS names are determined internally
2.0.0-prerelease8 We use .NET's
RuntimeInformation.IsOSPlatformmethod to determine the OS platform.E.g.:
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)returnstrueif the host OS is Microsoft Windows.2.0.0-prerelease8 For macOS and FreeBSD, we use the same method to check if the OS is macOS or FreeBSD, respectively.
2.0.0-prerelease8 For Linux, we use the same method to check if the OS is Linux, and then we read
/etc/os-releaseto determine the specific distribution. Inetc/os-release, we read theIDandID_LIKEfields to determine the distribution name. The predominant field isID_LIKE(Fedora is the only exception).Learn more about how we handle OS names:
OceanApocalypseStudios.RSML.Machine.LocalMachineclass. - 2.0.0-prerelease8
system-major-version-comparison-symbol2.0.0-prerelease8-
The comparison symbol to use for the system major version. This is optional; but
must be specified if you want to specify the argument that comes next in order; if not specified, it defaults to==. Can be one of the following:==,!=,<,>,<=,>=. It cannot be set toanyordefined. If not specified, it defaults to==.Supported Comparison Symbols:
- 2.0.0-prerelease8
==(equal to) - 2.0.0-prerelease8
!=(not equal to) - 2.0.0-prerelease8
<(less than) - 2.0.0-prerelease8
>(greater than) - 2.0.0-prerelease8
<=(less than or equal to) - 2.0.0-prerelease8
>=(greater than or equal to)
Allowed Wildcards:
- 2.0.0-prerelease8 None
- 2.0.0-prerelease8
system-major-version2.0.0-prerelease8-
The major version of the operating system to match against. This is optional; but must be specified if you want to specify the argument that comes next in order; if not specified, it matches all versions. Can be set to
anyto match all versions as well. Can, additionally, be set todefinedto match all versions as long as the OS version is recognized by the RSML implementation. Can be specified with or withoutsystem-major-version-comparison-symbol.Allowed Wildcards:
- 2.0.0-prerelease8
any(not a version, but matches all versions) - 2.0.0-prerelease8
defined(not a version, but matches all versions, as long as there's information about them available to RSML)
Note: Wildcards cannot be used together with comparison symbols. If you're planning on using comparison symbols, you need to specify actual versions. E.g., these are invalid syntax:
debian >= anyandosx != defined.Mapping for Windows versions:
Windows Version Name system-major-versionValueLast Changed Windows XP 5 2.0.0-prerelease8 Windows Vista 6 2.0.0-prerelease8 Windows 7 7 2.0.0-prerelease8 Windows 8 8 2.0.0-prerelease8 Windows 8.1 9 2.0.0-prerelease8 Windows 10 10 2.0.0-prerelease8 Windows 11 11 2.0.0-prerelease8 How OS versions are determined internally
2.0.0-prerelease8 We use our own implementation to determine the OS version, as to avoid relying on inaccurate methods such as
Environment.OSVersion, which is known to return wrong values on Windows 8.1 and later.2.0.0-prerelease8 On Windows, we use the
SOFTWARE\Microsoft\Windows NT\CurrentVersionregistry key. Inside it, we readCurrentBuildNumberand, if that's above 22000, we consider it Windows 11 (Windows 11 has its major set to 10, so this is necessary). Otherwise, we readCurrentMajorVersionNumberfor the major version (which, most likely will be 10 - since Windows 11 is handled before this check, you can be sure this "10" will always be Windows 10 and not Windows 11). Lastly, we fallback toCurrentVersion, where we consider the system version to be:- 2.0.0-prerelease8
5for Windows XP (ifCurrentVersionis5.1) - 2.0.0-prerelease8
6for Windows Vista (ifCurrentVersionis6.0) - 2.0.0-prerelease8
7for Windows 7 (ifCurrentVersionis6.1) - 2.0.0-prerelease8
8for Windows 8 (ifCurrentVersionis6.2) - 2.0.0-prerelease8
9for Windows 8.1 (ifCurrentVersionis6.3; in a future version, this will likely be changed to81to avoid confusion - we tried going with8.1but thesystem-major-versionargument must be an integer) - 2.0.0-prerelease8
x + yfor all others (wherexandyare such thatCurrentVersionisx.y- to be fixed in a future version to only considerxfor all other Windows versions)
2.0.0-prerelease8 On Linux, we read
/etc/os-releaseto determine the version. We read theVERSION_IDfield to get the version. The major version is considered to be the integer part before the first dot (.). If there's no dot, the whole value is considered the major version. IfVERSION_IDis not present, the version isnull, meaning usinganywon't match, butdefinedwill.2.0.0-prerelease8 On macOS, we use the
sw_verscommand to get the version. We read theProductVersionvalue and consider the major version to be the integer part before the first dot (.). If there's no dot, the whole value is considered the major version. If the command fails for any reason, the version isnull, meaning usinganywon't match, butdefinedwill.2.0.0-prerelease8 On FreeBSD, we use the
uname -rcommand to get the version. We consider the major version to be the integer part before the first dot (.). If there's no dot, the whole value is considered the major version. If the command fails for any reason, the version isnull, meaning usinganywon't match, butdefinedwill.Learn more about how we handle OS versions:
OceanApocalypseStudios.RSML.Machine.LocalMachineclass |LocalMachine.Windows.cs. - 2.0.0-prerelease8
cpu-architecture2.0.0-prerelease8-
The CPU architecture to match against. This is optional. Can be set to
anyto match all CPU architectures as well. Can, additionally, be set todefinedto match all CPU architectures as long as the architecture is recognized by the RSML implementation.Supported Architectures:
- 2.0.0-prerelease8
x86(32-bit x86 architecture) - 2.0.0-prerelease8
x64(64-bit x86 architecture) - 2.0.0-prerelease8
arm64(64-bit ARM architecture) - 2.0.0-prerelease8
arm32(32-bit ARM architecture; technically should be called justarm, but we usearm32for consistency) - 2.0.0-prerelease8
loongarch64(64-bit LoongArch architecture)
Allowed Wildcards:
- 2.0.0-prerelease8
any(not an architecture, but matches all architectures) - 2.0.0-prerelease8
defined(not an architecture, but matches all architectures, as long as there's information about them available to RSML)
- 2.0.0-prerelease8
value2.0.0-prerelease8-
The value to return or use as an error message, depending on the operator used. This is mandatory and must be enclosed in double quotes (
").
Examples¶
# Match-all
-> "This matches all OSes and architectures"
# System name
-> windows "This is Windows on any version and architecture"
# System name and CPU architecture
-> windows x64 "This is Windows on any version and x64 architecture"
# System name, major version and CPU architecture
-> windows 10 x64 "This is Windows 10 on x64 architecture"
# Full overload
-> windows >= 10 x64 "This is Windows 10 or above on x64 architecture"
-> windows defined any "This is Windows on any known version, and any architecture"
# note how we had to add "any"
# it's because there's no overload with only system name and system major version
# hence we having to use the cpu-architecture argument as well
# the only alternative in this acse was going with the full overload
# some might say full with "any" is clearer cuz the docujment becomes structured
# with a column-like look and it's easier to read and interpret what each argument is
# however, some will say simplicity is key
# here it goes, anyhoo:
# -> windows == defined any "This is Windows on any known version, and any architecture"
# wait a minute...
# THAT'S INVALID SYNTAX!
# a comparison symbol cannot be used with a wildcard - only actual versions!
# nearly blew up prod, Jerry!!
# for any other arguments, it'd be pretty much fine though, as only system version has that thing with comparison symbols :)
!> any any any "This error is thrown on any OS and architecture"
# this can be done at the end of the file if you want to ensure the machien can't just let it pass as "null" (this way, it forces evaluation to always end with an error if no other logic path matched)
# if you do it at the start of the file, well... good luck reaching any other logic path :D
# isn't that right, Jerry?
# 'any' matches all OSes/architectures/versions, even if unknown
-> any any any "This matches absolutely anything"
# 'defined' matches all known OSes/architectures/versions, but not unknown ones
-> defined defined defined "This does NOT match unknown OSes"
# in summary,
# while in the first case, RSML might not know what the OS is, what the architecture is, or what the version is and still match,
# in the second case, RSML MUST know what the OS is, what the architecture is, and what the version is in order to match
# any != defined
# don't forget that!
# Matching Fedora Linux versions 34 and above on x64 architecture
-> fedora >= 34 x64 "This is Fedora Linux 34 or above on x64 architecture"
# Matching Ubuntu Linux versions below 20 on any architecture
-> ubuntu < 20 any "This is Ubuntu Linux below version 20 on any architecture"
# Matching Windows versions not equal to 10 on arm64 architecture
-> windows != 10 arm64 "This is Windows on arm64 architecture, but NOT version 10"
# This RSML snippet matches Windows 10 or above on x64 architecture,
# and returns a specific value for that case.
# If the machine is not Windows 10 or above on x64, it throws an error.
-> windows >= 10 x64 "You are running Windows 10 or above on x64 architecture"
!> any any any "Unsupported OS or architecture detected"
# This RSML snippet demonstrates the use of wildcards and version comparisons.
# It matches any Linux distribution on any architecture,
# and also matches macOS versions 11 and above on arm64 architecture.
-> linux any any "You are running Linux on any architecture"
-> osx >= 11 arm64 "You are running macOS 11 or above on arm64 architecture"
!> any any any "Unsupported OS or architecture detected"
Special Actions¶
Special Actions are lines in RSML that start with the @ character, followed by the action name and an optional argument.
These are built into the language and cannot be changed whatsoever (unless you implement your own unstandardized actions; however, that is not recommended for compatibility reasons).
| Action Name | Argument Requirement | Functionality | Last Changed |
|---|---|---|---|
| EndAll | None | Ends evaluation immediately, with a null match. | 2.0.0-prerelease8 |
| Void | Optional | Does nothing; can be used as a no-op. | 2.0.0-prerelease8 |
| ThrowError | Mandatory | Throws an error with the given message. | 2.0.0-prerelease8 |
ThrowError vs !> operator
2.0.0-prerelease8 Note how the ThrowError special action is practically the same as !> any any any "<message>" logic path. The difference is internal only and quite neggligible for most use cases. Some will argue the first is clearer, while others will argue the second is clearer. Choose whichever you prefer!
Evaluation Process Flow¶
Strictly markup
Despite the usage of wording such as "return" and "interpret", RSML is purely declarative - it cannot execute, compile or transpile code.
RSML is evaluated from start to finish (see Advanced Representation of the Process Flow), meaning that the very first logic path with a return operator in it that matches will be used and the evaluation ends there. All the logic beyond that point is ignored completely, including comments and special actions.
Simplified Representation 2.0.0-prerelease8¶
---
title: Simplified Representation of the Evaluation Process Flow
---
flowchart LR
A[Start] --> B[Next line];
B -->F{Match?};
F -->|No| B;
F -->|Yes| C{Return operator?};
C -->|Yes| D[Return value + end];
C -->|No| E[Execute operator];
E -->B;
Advanced Representation 2.0.0-prerelease8¶
---
title: Advanced Representation of the Evaluation Process Flow
---
flowchart LR
A[Start] --> B[Next line];
B -->C{Valid?};
C -->|Yes| D{Type?};
C -->|No| B;
D -->|Logic Path| E{Match?};
D -->|Action| F{Ends eval?};
D -->|Comment| B;
E -->|Yes| G{Return op?};
E -->|No| B;
F -->|Yes| H[Finish];
F -->|No| I[Run action];
I -->B;
G -->|Yes| J[Return + end];
G -->|No| K[Execute operator];
K -->B;
Comments¶
We chose to talk about comments after the evalaution process, because, oh well, big surprise!, comments are ignored by the parser!
1.0.0 Comments are quite simple in RSML. If a # character is at the start of a line, that line is considered a comment and will be fully ignored by the parser.
Files¶
The recommended file extension for RSML files is .rsea, but other extensions may be used as well, such as .rsml. In fact, you can even use no extension at all, if you want to (ensures a lovely chaos mode).
Syntax Reference¶
This is a quick reference sheet on RSML's syntax.
Quick syntax reference
# This is a comment
-> "Match-all logic path"
-> windows "Match Windows on any version and architecture"
-> windows x64 "Match Windows on any version and x64 architecture"
-> windows 10 x64 "Match Windows 10 on x64 architecture"
-> windows >= 10 x64 "Match Windows 10 or above on x64 architecture"
!> osx < 11 arm64 "Throw error on macOS below version 11 on arm64 architecture"
-> any != 10 x86 "Don't confuse any..."
-> defined != 10 x86 "...with defined"
@EndAll
@Void
@ThrowError "This is an error message"
What might be coming soon?¶
Well, you haven't heard this from me, but there are plans to add more features to RSML in future versions, such as:
- Logical INBETWEEN operator in logic paths, to allow for more complex matching conditions.
- Line-NOT and Argument-NOT operators to negate matches.
- Support for more operating systems and CPU architectures as they become relevant.
- Enhanced error handling and reporting for better debugging.
- Increased strictness levels to enforce more rigorous syntax and evaluation rules and avoid ambiguity and silent line skips.
- More special actions to provide additional functionality during evaluation.
- Hot fixes for certain system version detection methods to improve accuracy.
- Internal-only refactors and optimizations to improve performance, maintainability and reduce the difficulty of implementing new features. We will try to do this with as least API breaking changes as possible. Hopefully, zero language changes (obviously, not because we care about you, but because I don't want to rewrite this documentation, love you too cutie <3 - jk).