@randsum/notation
Version:
Dice notation parser and types for the @randsum ecosystem
318 lines (317 loc) • 12.3 kB
TypeScript
interface ComparisonOptions {
/** Threshold for "greater than" comparisons (strict: roll > N) */
greaterThan?: number;
/** Threshold for "greater than or equal to" comparisons (roll >= N) */
greaterThanOrEqual?: number;
/** Threshold for "less than" comparisons (strict: roll < N) */
lessThan?: number;
/** Threshold for "less than or equal to" comparisons (roll <= N) */
lessThanOrEqual?: number;
/** Exact values to match */
exact?: number[];
}
interface DropOptions extends ComparisonOptions {
/** Number of highest dice to drop */
highest?: number;
/** Number of lowest dice to drop */
lowest?: number;
}
interface KeepOptions {
/** Number of highest dice to keep */
highest?: number;
/** Number of lowest dice to keep */
lowest?: number;
}
interface RerollOptions extends ComparisonOptions {
/** Maximum number of rerolls allowed */
max?: number;
}
interface ReplaceOptions {
/** Value or comparison to match for replacement */
from: number | ComparisonOptions;
/** Value to replace matched rolls with */
to: number;
}
interface UniqueOptions {
/** Values that are allowed to repeat */
notUnique: number[];
}
interface SuccessCountOptions {
/** Threshold for counting successes (rolls >= this value) */
threshold: number;
/** Optional: threshold for counting botches/failures (rolls <= this value) */
botchThreshold?: number;
}
type ModifierConfig = number | boolean | ComparisonOptions | DropOptions | KeepOptions | ReplaceOptions | ReplaceOptions[] | RerollOptions | UniqueOptions | SuccessCountOptions;
interface ModifierOptions {
/** Cap roll values to a range */
cap?: ComparisonOptions;
/** Drop dice from the result */
drop?: DropOptions;
/** Keep dice from the result (complement to drop) */
keep?: KeepOptions;
/** Replace specific values */
replace?: ReplaceOptions | ReplaceOptions[];
/** Reroll dice matching conditions */
reroll?: RerollOptions;
/** Ensure unique values (true or options) */
unique?: boolean | UniqueOptions;
/** Exploding dice: reroll and add on max value */
explode?: boolean | number;
/** Compounding exploding: add to triggering die instead of creating new dice */
compound?: boolean | number;
/** Penetrating exploding: subtract 1 from each subsequent explosion */
penetrate?: boolean | number;
/** Count successes instead of summing (for dice pool systems) */
countSuccesses?: SuccessCountOptions;
/** Multiply dice result (before +/- arithmetic) */
multiply?: number;
/** Add a fixed value to the total */
plus?: number;
/** Subtract a fixed value from the total */
minus?: number;
/** Multiply final total (after all other modifiers) */
multiplyTotal?: number;
}
/**
* The result of parsing a dice notation string.
* Similar to RollOptions but with sides always numeric and
* quantity/arithmetic always present.
*/
interface ParsedNotationOptions {
/** Number of dice to roll */
quantity: number;
/** How this roll combines with others */
arithmetic: "add" | "subtract";
/** Number of sides on each die */
sides: number;
/** Modifiers to apply to the roll */
modifiers?: ModifierOptions;
}
/**
* Template literal type for dice notation strings.
*/
type DiceNotation = `${number}${"d" | "D"}${number}${string}`;
/**
* Configuration options for a dice roll.
*
* @template T - Type for custom dice faces (defaults to string)
*/
interface RollOptions<T = string> {
/** Number of dice to roll (default: 1) */
quantity?: number;
/** How this roll combines with others: 'add' or 'subtract' (default: 'add') */
arithmetic?: "add" | "subtract";
/** Number of sides, or array of custom face values */
sides: number | T[];
/** Modifiers to apply to the roll (drop, reroll, explode, etc.) */
modifiers?: ModifierOptions;
/**
* Optional identifier for this roll in multi-roll expressions.
* Default keys are auto-generated as "Roll 1", "Roll 2", etc.
*/
key?: string | undefined;
}
/**
* Successful validation result.
*/
interface ValidValidationResult {
/** Indicates successful validation */
valid: true;
/** Original input as DiceNotation */
argument: DiceNotation;
/** Human-readable descriptions for each roll */
description: string[][];
/** Parsed roll options for each roll */
options: ParsedNotationOptions[];
/** Notation strings for each roll */
notation: DiceNotation[];
/** No error on success */
error: null;
}
/**
* Error information from validation.
*/
interface ValidationErrorInfo {
/** Description of what's wrong */
message: string;
/** The input that failed validation */
argument: string;
}
/**
* Failed validation result.
*/
interface InvalidValidationResult {
/** Indicates failed validation */
valid: false;
/** Original input string */
argument: string;
/** Error information */
error: ValidationErrorInfo;
}
/**
* Result of notation validation.
*/
type ValidationResult = ValidValidationResult | InvalidValidationResult;
/**
* Notation-only half of a modifier definition.
*
* Covers pure string transformation concerns:
* - Pattern matching
* - Parsing notation into options
* - Converting options back to notation
* - Human-readable description generation
*
* @template TOptions - The type of options this modifier's notation represents
*/
interface NotationSchema<TOptions = unknown> {
/** Unique identifier matching the key in ModifierOptions */
name: keyof ModifierOptions;
/**
* Execution priority (lower = earlier).
* Shared with ModifierDefinition to determine parse ordering.
*/
priority: number;
/**
* Regex pattern to match this modifier in a notation string.
* Should NOT include 'g' flag - that is handled by the registry.
*/
pattern: RegExp;
/**
* Parse notation string into modifier options.
* Receives the full notation string to allow finding multiple matches.
* Returns a partial ModifierOptions with just this modifier's key.
*/
parse: (notation: string) => Partial<ModifierOptions>;
/**
* Convert options to notation string (e.g., "C{>5}").
* Returns undefined if options would produce empty notation.
*/
toNotation: (options: TOptions) => string | undefined;
/**
* Convert options to human-readable description strings.
* Returns array of descriptions (some modifiers produce multiple lines).
*/
toDescription: (options: TOptions) => string[];
}
/**
* Define a notation schema with type checking.
* This is a type-safe factory for creating notation schemas.
*
* @param schema - Complete notation schema
* @returns The schema unchanged (identity function for type checking)
*/
declare function defineNotationSchema<TOptions>(schema: NotationSchema<TOptions>): NotationSchema<TOptions>;
declare function notationToOptions(notationString: string): ParsedNotationOptions[];
declare function listOfNotations(notationString: string, coreMatches: RegExpMatchArray[]): string[];
/**
* Type guard that checks if a value is valid dice notation.
*
* @param argument - Value to check
* @returns True if argument is valid dice notation, false otherwise
*
* @example
* ```ts
* if (isDiceNotation("4d6L")) {
* // TypeScript knows this is DiceNotation here
* }
* ```
*/
declare function isDiceNotation(argument: unknown): argument is DiceNotation;
/**
* Error thrown when a string is not valid dice notation.
*/
declare class NotationParseError extends Error {
readonly code: "INVALID_NOTATION";
readonly suggestion: string | undefined;
constructor(notation: string, reason: string, suggestion?: string);
}
/**
* Validates a string as DiceNotation, throwing if invalid.
*
* @param input - String to validate
* @returns The input narrowed to DiceNotation
* @throws NotationParseError if input is not valid dice notation
*/
declare function notation(input: string): DiceNotation;
/**
* Validates dice notation and returns parsed information.
*
* @param notation - String to validate as dice notation
* @returns ValidationResult with valid flag and error (if invalid)
*/
declare function validateNotation(notation: string): ValidationResult;
/**
* Convert modifier options to a notation string suffix.
* Processes modifiers in priority order (cap → multiplyTotal).
*/
declare function modifiersToNotation(modifiers: ModifierOptions | undefined): string;
/**
* Convert modifier options to an array of human-readable description strings.
* Processes modifiers in priority order (cap → multiplyTotal).
*/
declare function modifiersToDescription(modifiers: ModifierOptions | undefined): string[];
declare function optionsToSidesFaces<T>({ sides }: RollOptions<T>): {
sides: number;
faces?: T[];
};
/**
* Converts roll options to RANDSUM dice notation string.
*
* @template T - Type for custom dice faces
* @param options - Roll options to convert
* @returns Dice notation string (e.g., "4d6L", "2d20H+5")
* @throws NotationParseError if generated notation is invalid
*/
declare function optionsToNotation<T = string>(options: RollOptions<T>): DiceNotation;
/**
* Converts roll options to a human-readable description.
*
* @template T - Type for custom dice faces
* @param options - Roll options to describe
* @returns Array of description strings
*/
declare function optionsToDescription<T = string>(options: RollOptions<T>): string[];
/**
* Detects common typos in dice notation.
*
* @param notation - Invalid notation to check
* @returns Suggested correction, or undefined if no suggestion
*/
declare function suggestNotationFix(notation: string): string | undefined;
declare const coreNotationPattern: RegExp;
/**
* Parses a condition string into comparison options.
*/
declare function parseComparisonNotation(conditionString: string): ComparisonOptions;
/**
* Checks if a comparison options object has any conditions defined.
*/
declare function hasConditions(options: ComparisonOptions): boolean;
declare function formatComparisonDescription({ greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, exact }: ComparisonOptions): string[];
declare function formatComparisonNotation({ greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, exact }: ComparisonOptions): string[];
type TokenType = "core" | "dropLowest" | "dropHighest" | "dropCondition" | "keepHighest" | "keepLowest" | "reroll" | "explode" | "compound" | "penetrate" | "cap" | "replace" | "unique" | "countSuccesses" | "plus" | "minus" | "multiply" | "multiplyTotal" | "unknown";
interface Token {
readonly text: string;
readonly type: TokenType;
readonly start: number;
readonly end: number;
readonly description: string;
}
declare function tokenize(notation: string): readonly Token[];
declare function formatHumanList(values: number[]): string;
declare const capSchema: NotationSchema<ComparisonOptions>;
declare const dropSchema: NotationSchema<DropOptions>;
declare const keepSchema: NotationSchema<KeepOptions>;
declare const replaceSchema: NotationSchema<ReplaceOptions | ReplaceOptions[]>;
declare const rerollSchema: NotationSchema<RerollOptions>;
declare const explodeSchema: NotationSchema<boolean | number>;
declare const compoundSchema: NotationSchema<boolean | number>;
declare const penetrateSchema: NotationSchema<boolean | number>;
declare const uniqueSchema: NotationSchema<boolean | UniqueOptions>;
declare const countSuccessesSchema: NotationSchema<SuccessCountOptions>;
declare const multiplySchema: NotationSchema<number>;
declare const plusSchema: NotationSchema<number>;
declare const minusSchema: NotationSchema<number>;
declare const multiplyTotalSchema: NotationSchema<number>;
export { validateNotation, uniqueSchema, tokenize, suggestNotationFix, rerollSchema, replaceSchema, plusSchema, penetrateSchema, parseComparisonNotation, optionsToSidesFaces, optionsToNotation, optionsToDescription, notationToOptions, notation, multiplyTotalSchema, multiplySchema, modifiersToNotation, modifiersToDescription, minusSchema, listOfNotations, keepSchema, isDiceNotation, hasConditions, formatHumanList, formatComparisonNotation, formatComparisonDescription, explodeSchema, dropSchema, defineNotationSchema, countSuccessesSchema, coreNotationPattern, compoundSchema, capSchema, ValidationResult, ValidationErrorInfo, ValidValidationResult, UniqueOptions, TokenType, Token, SuccessCountOptions, RollOptions, RerollOptions, ReplaceOptions, ParsedNotationOptions, NotationSchema, NotationParseError, ModifierOptions, ModifierConfig, KeepOptions, InvalidValidationResult, DropOptions, DiceNotation, ComparisonOptions };