A C++20 reimplementation of the DEC VMS CLI$ system routines for parsing DCL-style commands on Linux.
If you've used VMS, you know DCL. If you haven't -- DCL commands look like this:
COPY source.txt dest.txt /LOG /CONFIRM
DIRECTORY /OUTPUT=listing.txt /SELECT=(*.cpp,*.h)
SET DEFAULT "/home/user"
CDL lets you define, parse, query, and dispatch these commands programmatically, with the same semantics as the original VMS facility -- minimum unique abbreviation, qualifier negation, structured error reporting, and all.
# Build
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
# Test
cd build && ctest --output-on-failure
# Install
cmake --install build --prefix /usr/local# Option 1: subdirectory
add_subdirectory(path/to/dec-cdl)
target_link_libraries(your_target PRIVATE cdl::cdl)
# Option 2: installed
find_package(cdl REQUIRED)
target_link_libraries(your_target PRIVATE cdl::cdl)#include <cdl/cdl.h>
#include <iostream>
using namespace cdl;
int main() {
CliCommandTable table{"MY_APP"};
// Define a COPY verb with parameters and qualifiers
CliVerb copy;
copy.name = "COPY";
copy.parameters.push_back({"P1", CliValueType::Required, true, "From: "});
copy.parameters.push_back({"P2", CliValueType::Required, true, "To: "});
copy.qualifiers.push_back({"LOG", CliValueType::None, /*negatable=*/true});
copy.action = [](const ParsedCommand& cmd) -> CliStatus {
std::string from, to;
cli_get_value(cmd, "P1", from);
cli_get_value(cmd, "P2", to);
std::cout << "Copying " << from << " -> " << to << "\n";
return CliStatus::Success;
};
cli_add_verb(table, std::move(copy));
// Parse and dispatch
ParsedCommand cmd;
CliStatus status = cli_parse(table, "COP source.txt dest.txt /LOG", cmd);
if (cli_success(status)) {
if (cli_present(cmd, "LOG") == CliStatus::Present)
std::cout << "Logging enabled\n";
cli_dispatch(cmd); // calls the action handler
} else if (cmd.error) {
std::cerr << cmd.error->message << "\n";
}
}COP resolves to COPY via minimum unique abbreviation. /LOG is recognized as a qualifier. The action handler receives the fully parsed command.
- Verb abbreviation --
DIRmatchesDIRECTORYif unambiguous, with configurable minimum length - Qualifier syntax --
/NAME=VALUE,/NAME:VALUE, negation with/NONAME, parenthesized lists/QUAL=(A,B,C) - Parameter handling -- positional parameters (P1, P2, ...) with required/optional/list support
- Subcommands -- multi-word verbs like
SET DEFAULT - Qualifier placement -- Global, Local (per-parameter), and Positional modes
- Default qualifiers -- automatically applied unless explicitly overridden or negated
- Rightmost wins -- duplicate qualifiers resolved by last occurrence
- VMS status codes -- actual condition values with low-bit success convention
- Structured errors --
CliErrorwith status code, message, offending token, and position - Case insensitive -- unquoted text is case-folded; quoted strings preserve case
- Zero dependencies -- standalone C++20, no external runtime libraries
All symbols are in the cdl namespace.
| Function | VMS Equivalent | Purpose |
|---|---|---|
cli_parse() |
CLI$DCL_PARSE |
Parse command line against a command table |
cli_present() |
CLI$PRESENT |
Check qualifier/parameter presence |
cli_get_value() |
CLI$GET_VALUE |
Retrieve a single value |
cli_get_values() |
-- | Retrieve all values (list qualifiers) |
cli_dispatch() |
CLI$DISPATCH |
Invoke verb's action handler |
cli_dispatch_type() |
-- | Query dispatch method (Action/Image/None) |
cli_find_verb() |
-- | Look up verb with abbreviation support |
cli_find_qualifier() |
-- | Look up qualifier with abbreviation support |
cli_validate_table() |
-- | Check table for errors (duplicates, ordering) |
cli_status_string() |
-- | Human-readable string for any status code |
#include <cdl/cdl.h> // Everything
#include <cdl/cli_types.h> // Types, status codes, structs
#include <cdl/cli_tables.h> // Table construction, lookup, validation
#include <cdl/cli_parse.h> // Parsing
#include <cdl/cli_present.h> // Presence checking
#include <cdl/cli_get_value.h> // Value retrieval
#include <cdl/cli_dispatch.h> // Action dispatchingStatus Codes
Status codes use actual VMS condition values. The low bit indicates success -- use cli_success() to test:
if (cli_success(status)) { /* ok */ }Success (low bit SET): Success, Present, Defaulted, LocPres, Concat, Comma
Warning (low bit CLEAR): Absent, Negated, LocNeg
Error (low bit CLEAR): AbVerb, AbKeyw, InsFPreq, IvKeyw, IvValue, IvVerb, IvQual, MaxParm, NoComd, NoList, NoVal, NotNeg, ValReq, OneVal, Conflict, InvRout
Note: Negated has the low bit CLEAR intentionally. This matches VMS behavior -- a negated qualifier is a warning, not a success. Code must check for Negated explicitly if it needs to distinguish "user said no" from "not specified."
DCL Parsing Rules
CDL replicates these DCL behaviors:
- Case insensitivity --
SHOW,show, andShoware equivalent. Quoted text preserves case. - Abbreviation --
SHresolves toSHOWif unambiguous. Ambiguous input returnsAbVerb. Verbs and qualifiers can setmin_lengthto require a minimum abbreviation length. - Qualifier negation --
/NOFULLnegates theFULLqualifier. Non-negatable qualifiers returnNotNeg. - Default qualifiers -- Applied automatically if not explicitly specified or negated.
- Value enforcement -- Required qualifiers without values return
ValReq. Single-value qualifiers with lists returnOneVal. - Rightmost wins -- last occurrence of a qualifier takes precedence.
- Unknown rejection -- undefined qualifiers return
IvQual. - Quoted strings --
"..."preserves case and special characters.""produces a literal". Linux paths must be quoted since/is the qualifier delimiter. - Comments --
!starts a comment (outside quotes). - Lists --
/QUAL=(A,B,C)for multi-valued qualifiers. - Colon syntax --
/OUTPUT:file.txtis equivalent to/OUTPUT=file.txt.
Qualifier Placement
| Placement | Behavior |
|---|---|
CliPlacement::Global |
Can appear anywhere, applies to entire command (default) |
CliPlacement::Local |
Only valid after a parameter, applies to that parameter |
CliPlacement::Positional |
Global if after verb, local if after a parameter |
Use cli_present(cmd, name, parameter_index) to check local qualifier presence.
Core Types
// Action handler signature
using CliAction = std::function<CliStatus(const ParsedCommand&)>;
// Parsed qualifier/parameter state
struct ParsedValue {
std::vector<std::string> values; // All values (single or list)
bool negated; // /NOXXX
bool present; // Explicitly specified
bool defaulted; // Present by default
int parameter_index; // -1 = global, >=0 = after parameter N
};
// Structured error information
struct CliError {
CliStatus status; // Error code
std::string message; // Human-readable description
std::string token; // Offending token
size_t position; // Character position in command line
};CliValueType options: None, Optional, Required, List, Keyword, File, Number, Rest, QuotedString
- C++20 compiler (GCC 12+, Clang 15+)
- CMake 3.20+
- GoogleTest (fetched automatically)
| Option | Default | Description |
|---|---|---|
CDL_BUILD_TESTS |
ON |
Build the test suite |
CDL_BUILD_EXAMPLES |
ON |
Build example programs |
# Minimal build (library only)
cmake -B build -DCDL_BUILD_TESTS=OFF -DCDL_BUILD_EXAMPLES=OFF
cmake --build buildinclude/cdl/ Public headers (the API contract)
src/ Implementation
tests/ GoogleTest suite (89 tests)
examples/ Working usage examples
docs/ Reference documentation
Contributions are welcome. Please ensure all tests pass (ctest --output-on-failure) before submitting a PR. The CI runs GCC and Clang in both Debug and Release configurations.