From 3896d1698b2b21a2b896d356f0848ca010280a6a Mon Sep 17 00:00:00 2001 From: "Vincent Yanzee J. Tan" Date: Wed, 11 Jun 2025 10:01:51 +0800 Subject: [PATCH] rm: legacy sift --- src/sift/defs.ts | 57 -------- src/sift/index.ts | 79 ----------- src/sift/parser.ts | 342 --------------------------------------------- src/sift/tokens.ts | 109 --------------- src/sift/types.ts | 68 --------- 5 files changed, 655 deletions(-) delete mode 100644 src/sift/defs.ts delete mode 100644 src/sift/index.ts delete mode 100644 src/sift/parser.ts delete mode 100644 src/sift/tokens.ts delete mode 100644 src/sift/types.ts diff --git a/src/sift/defs.ts b/src/sift/defs.ts deleted file mode 100644 index aee4688..0000000 --- a/src/sift/defs.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type * as bricklib from '../bricklib/index.js'; -import type { ArgTokenStream } from './tokens.js'; - -/** - * A command argument parser. - */ -export type TypeParser = (def: CmdArgument, args: ArgTokenStream) => T; - -/** - * Result of parsing. - */ -export type ParseResult> = T; - -/** - * A positional/option-argument definition. - */ -export interface CmdArgument -{ - id: PropertyKey, - help?: string | bricklib.rawtext.RawString, - name?: string, - - type: TypeParser, - optional?: boolean, - default?: T, - - [k: PropertyKey]: any, -} - -/** - * A flag/option. - */ -export interface CmdOption -{ - id: PropertyKey, - help?: string | bricklib.rawtext.RawString, - - long?: string[], - short?: string, - args?: CmdArgument[], -} - -/** - * A command/sub-command. - */ -export interface CmdVerb -{ - id: PropertyKey, - help?: string | bricklib.rawtext.RawString, - - name: string, - aliases?: string[], - - args?: CmdArgument[], - options?: CmdOption[], - subcmds?: CmdVerb[], -} diff --git a/src/sift/index.ts b/src/sift/index.ts deleted file mode 100644 index c38a48c..0000000 --- a/src/sift/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Sift -- A flexible command parser for bricklib. - * This plugin is part of the bricklib project. - */ - -/* TODO: help-text gen, cmd builder, and more type parsers... */ - -import * as bricklib from '../bricklib/index.js'; -import { Player } from '@minecraft/server'; -import { parseVerb } from './parser.js'; -import { ArgTokenStream } from './tokens.js'; -import type { CmdVerb, ParseResult } from './defs.ts'; - - -export type * from './defs.ts'; -export * from './parser.js'; -export * from './tokens.js'; -export * as parsers from './types.js'; - -bricklib.plugin.newPlugin('sift', () => { - /* no-op */ -}); - - -/** - * Parse a custom command. - * @param def The command parsing definition. - * @param args Array of token streams. - * @returns The parsing result. - * @throws This function can throw errors. - */ -export function parseCommand(def: CmdVerb, args: string[]): ParseResult -{ - const toks = new ArgTokenStream(args); - toks.consume(); /* skip the command name */ - return parseVerb(def, toks, []); -} - -/** - * Make a command def that you can pass to - * {@link bricklib.command.CommandManager.registerCommand}. - * @param def The command definition. - * @param fn The callback. - * @returns Array of args. - * @example - * ```ts - * import * as sift from './sift/index.js'; - * const def = { - * id: 'echo', - * name: 'echo', - * args: [ - * { - * id: 'text', - * type: sift.parsers.string(), - * } - * ] - * }; - * - * mgr.registerCommand(...sift.makeCommand(def, (args, src) => { - * src.sendMessage(args.text); - * return 0; - * })); - * ``` - */ -export function makeCommand( - def: CmdVerb, - fn: (args: ParseResult, src: Player) => number - ): [string[], bricklib.command.CommandCallback] -{ - const out: [string[], bricklib.command.CommandCallback] = [null, null]; - out[0] = [def.name]; - if (def.aliases) - out[0] = out[0].concat(def.aliases); - out[1] = (src, args) => { - const result = parseCommand(def, args); - return fn(result, src); - }; - return out; -} diff --git a/src/sift/parser.ts b/src/sift/parser.ts deleted file mode 100644 index 4454d90..0000000 --- a/src/sift/parser.ts +++ /dev/null @@ -1,342 +0,0 @@ -import type { - CmdArgument, - CmdVerb, - CmdOption, - ParseResult, -} from './defs.ts'; -import { ArgTokenStream } from './tokens.js'; - -/** - * Parse args. - * @param argDef The argument definition. - * @param args The token stream. - * @returns Parse result. - * @throws This function can throw errors. - */ -export function parseArg( - argDef: CmdArgument, args: ArgTokenStream): R -{ - if (args.isEnd && !argDef.optional) - throw 'Unexpected end of input'; - return argDef.type(argDef, args); -} - -/** - * Parse the arguments of an option. - * @param defs The arg defs of an option. - * @param args The token stream. This function will only set the - * default values of option-arg defs when this parameter is null. - * @param result Where to set the results. - * @param isAdj Force the first arg to succed parsing. This is useful - * when the first arg in the option is adjacent (--opt=VAL, -mVAL). - * @param name Name of the argument (for better error reporting). - * @throws This function can throw errors. - */ -export function parseLooseArgs( - argDefs: CmdArgument[], args: ArgTokenStream, result: ParseResult, - isAdj: boolean, name: string): void -{ - if (!argDefs?.length && isAdj) - throw 'Option ' + name + ' does not need any argument'; - - let stopArgs = !args; - argDefs?.forEach((argDef, idx) => { - /* process optional args that didn't match */ - if (stopArgs) { - result[argDef.id] = argDef.default; - checkArgDefOrder(argDef, true); - return; - } - - /* process required args */ - try { - args.push(); - result[argDef.id] = parseArg(argDef, args); - args.complete(); - } - catch (e) { - if (!argDef.optional || (isAdj && idx == 0)) - throw e; - stopArgs = true; - args.pop(); - } - }); -} - -/** - * Parse a long option. - * @param defs The options list. - * @param args The token stream. - * @param result Where to set the results. - * @throws This function can throw errors. - */ -export function parseLongOption( - optDefs: CmdOption[], args: ArgTokenStream, result: ParseResult): void -{ - const arg = args.current; - - if (!arg.startsWith('--')) - return; - args.consume(); - - const [optName, eqArg] = arg.slice(2).split('='); - const optDef = optDefs?.find(v => v.long?.includes(optName)); - - if (!optDef) - throw 'Unrecognized flag: --' + optName; - - /* handle: --flag=arg */ - if (typeof eqArg === 'string') - args.insert(eqArg); - - /* found option! */ - if (typeof result[optDef.id] !== 'number') - result[optDef.id] = 0; - result[optDef.id]++; - - parseLooseArgs(optDef.args, args, result, - typeof eqArg === 'string', '--' + optName); -} - -/** - * Parse a short option. - * @param defs The options list. - * @param args The token stream. - * @param result Where to set the results. - * @throws This function can throw errors. - */ -export function parseShortOption( - optDefs: CmdOption[], args: ArgTokenStream, result: ParseResult): void -{ - const arg = args.current; - - if (!arg.startsWith('-')) - return; - args.consume(); - - for (let i = 1; i < arg.length; i++) { - const char = arg[i]; - const optDef = optDefs?.find(v => v.short?.includes(char)); - - if (!optDef) - throw 'Unrecognized flag: -' + char; - - /* found option! */ - if (typeof result[optDef.id] !== 'number') - result[optDef.id] = 0; - result[optDef.id]++; - - if (!optDef.args?.[0]) - continue; - - /* handle: -mVALUE (where -m is the current opt) */ - const nearArg = arg.slice(i+1); - if (nearArg.length) - args.insert(nearArg); - - parseLooseArgs(optDef.args, args, result, - nearArg.length > 0, '-' + char); - break; - } -} - -/** - * Parse a command/sub-command. - * @param def The command/sub-command definition. - * @param args The token stream. This function will only set the - * default values of positional-arg defs when this parameter is null. - * @param upOpts Starting option list. Can be used to inherit options - * from a parent command. - * @returns The parsing result. - * @throws This function can throw errors. - */ -export function parseVerb( - cmdDef: CmdVerb, args: ArgTokenStream, upOpts: CmdOption[]): ParseResult -{ - const result: ParseResult = Object.create(null); - let argIdx = 0; - let hasSubCmd = false; /* whether we had processed a subcmd */ - let stopOpts = false; /* stop parsing of flags/options */ - let endReqArgs = false; /* end of required args (no more - required args are expected) */ - if (cmdDef.options) - upOpts = upOpts.concat(cmdDef.options); - - /* process options, positional args, and subverbs */ - while (args && !args.isEnd) { - const arg = args.current; - - if (arg[0] == '-' && !stopOpts) { - /* a short option */ - if (arg[1] != '-') { - parseShortOption(upOpts, args, result); - continue; - } - /* double-dash (--) */ - if (arg.length == 2) { - args.consume(); - stopOpts = true; - continue; - } - parseLongOption(upOpts, args, result); - continue; - } - - /* parse positional parameters */ - if (argIdx < cmdDef.args?.length) { - const argDef = cmdDef.args[argIdx++]; - endReqArgs = checkArgDefOrder(argDef, endReqArgs); - result[argDef.id] = parseArg(argDef, args); - continue; - } - - /* process subcmds */ - if (cmdDef.subcmds?.length) { - parseSubVerb(cmdDef.subcmds, args, result, upOpts); - hasSubCmd = true; - break; - } - - throw 'Too many arguments: ' + arg; - } - - /* no more tokens left... */ - setVerbDefaults(cmdDef, result, argIdx, !hasSubCmd, endReqArgs, new Set()); - - /* yey! verb has been processed successfuly! */ - result[cmdDef.id] = true; - return result; -} - -/** - * Helper function to assert whether there are required args after - * the optional ones - * @param argDef The current argument definition to check. - * @param endReqArgs Whether there are no more required positionals - * that is expected. - * @returns The new value for endReqArgs - * @throws This function *will* throw an error if it finds - * a required positional argument after the optional ones. - */ -export function checkArgDefOrder( - argDef: CmdArgument, endReqArgs: boolean): boolean -{ - if (!argDef.optional && endReqArgs) - throw new Error('Parse definition error: Required arguments must ' + - 'not come after optional ones'); - return !!argDef.optional; -}; - -/** - * Set the defaults of a command. (i.e., there are no more tokens but - * there are still optional arg defs). - * @param cmdDef The command definution. - * @param result Where to set the results. - * @param argIdx The index of the start of the unprocessed - * positional arg defs. - * @param doSubCmds Whether you ran out of tokens without - * yet parsing subcommands. - * @param endReqArgs Whether there are no more required - * args that is expected. - * @param processedSubs A set which contains all the - * subcommands whose defaults are already set. - * @throws This function can throw errors. - */ -export function setVerbDefaults( - cmdDef: CmdVerb, result: ParseResult, argIdx: number, - doSubCmds: boolean, endReqArgs: boolean, processedSubs: Set): void -{ - /* prevents infinite recursion on nested cyclic subcmds */ - processedSubs.add(cmdDef); - - /* set the defaults of other positional args */ - while (argIdx < cmdDef.args?.length) { - const argDef = cmdDef.args[argIdx++]; - endReqArgs = checkArgDefOrder(argDef, endReqArgs); - if (!argDef.optional) /* need more args!! */ - throw 'Insufficient arguments'; - result[argDef.id] = argDef.default; - } - - /* no subcmd found. set the defaults of unnamed subcmds */ - if (doSubCmds && cmdDef.subcmds?.length) - for (const verbDef of cmdDef.subcmds) { - if (verbDef.name?.length || !isAllArgsOptional(verbDef.args)) - continue; - /* find the first subverb where *all* positionals are optional. - we don't wanna set the defaults of unnamed subs that requires - user input */ - if (!processedSubs.has(verbDef)) - setVerbDefaults(verbDef, result, 0, true, true, processedSubs); - break; - } - - /* this has optionals, and we parsed it successfuly! */ - result[cmdDef.id] = true; -} - -/** - * Parse subcommands. - * @param cmdDefs Subcommand definitions. - * @param args The token stream. - * @param result Where to write the results. - * @param upOpts Unnamed sub-commands has to inherit its parent's - * options. - * @throws This function can throw errors. - */ -export function parseSubVerb( - cmdDefs: CmdVerb[], args: ArgTokenStream, result: ParseResult, - upOpts: CmdOption[]): void -{ - if (!cmdDefs) - return; - const name = args.current; - const unNamedSubs = []; - - for (const verbDef of cmdDefs) { - /* collect unnamed subcmds */ - if (!verbDef.name?.length) { - unNamedSubs.push(verbDef); - continue; - } - - if (verbDef.name != name && !verbDef.aliases?.includes(name)) - continue; - args.consume(); - - /* exact match found */ - result[verbDef.id] = parseVerb(verbDef, args, []); - return; - } - - /* there's really no match */ - if (!unNamedSubs.length) - throw 'Unknown subcommand: ' + name; - - /* trial parsing for unnamed subs */ - let err; - for (const verbDef of unNamedSubs) { - try { - args.push(); - result[verbDef.id] = parseVerb(verbDef, args, upOpts); - args.complete(); - return; - } - catch (e) { - args.pop(); - if (!err) - err = e; - } - } - throw err; -} - -/** - * Check whether all the arg defs in the given list is optional. - * @param argDefs List of arg definitions. - * @returns True if all args are optional. - */ -export function isAllArgsOptional(argDefs: CmdArgument[]): boolean -{ - return (argDefs ?? []).every(def => !!def.optional); -} diff --git a/src/sift/tokens.ts b/src/sift/tokens.ts deleted file mode 100644 index c1b343e..0000000 --- a/src/sift/tokens.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @class - * A stream of argument tokens. - */ -export class ArgTokenStream -{ - /** - * @private - */ - private _args: string[] = []; - private _pos: number = 0; - private _state: { pos: number, args: string[] }[] = []; - - /** - * @constructor - * Creates a new ArgTokenStream instance. - * @param args The arg array. - */ - constructor(args: string[]) - { - this._args = args; - } - - /** - * Get a copy of the arg array. - * @returns A copy of the arg array. - */ - public getCopy(): string[] - { - return [...this._args]; - } - - /** - * Consume the current arg. - * @returns Itself. - */ - public consume(): this - { - if (this._pos < this._args.length) - this._pos++; - return this; - } - - /** - * Insert a token at the current position. - * @param tok The token to insert. - * @returns Itself. - */ - public insert(tok: string): this - { - this._args.splice(this._pos, 0, tok); - return this; - } - - /** - * Get the current arg. - * @returns The current arg. - */ - public get current(): string - { - return this._args[this._pos]; - } - - /** - * Whether there are no more tokens left. - * @returns True if there's no more tokens. - */ - public get isEnd(): boolean - { - return this._pos >= this._args.length; - } - - /** - * Push the current pos state. - * @returns Itself. - */ - public push(): this - { - this._state.push({ - pos: this._pos, - args: this.getCopy(), - }); - return this; - } - - /** - * Complete the subparsing. - * @returns itself. - */ - public complete(): this - { - this._state.pop(); - return this; - } - - /** - * Pop pos state. - * @returns Itself. - */ - public pop(): this - { - if (this._state.length) { - const state = this._state.pop(); - this._pos = state.pos; - this._args = state.args; - } - return this; - } -} diff --git a/src/sift/types.ts b/src/sift/types.ts deleted file mode 100644 index 9b21cf7..0000000 --- a/src/sift/types.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { CmdArgument, TypeParser } from './defs.ts'; -import { ArgTokenStream } from './tokens.js'; - -export function string(): TypeParser -{ - return (_: CmdArgument, args: ArgTokenStream): string => { - const arg = args.current; - args.consume(); - return arg; - }; -} - -export function float(): TypeParser -{ - return (_: CmdArgument, args: ArgTokenStream): number => { - const arg = args.current; - args.consume(); - /* try parse float */ - const val = parseFloat(arg); - if (isNaN(val)) - throw 'invalid float: ' + arg; - return val; - }; -} - -export function bool(): TypeParser -{ - return (_: CmdArgument, args: ArgTokenStream): boolean => { - const arg = args.current; - args.consume(); - if (arg == 'true') return true; - if (arg == 'false') return false; - throw 'invalid boolean: ' + arg; - }; -} - -export function int(): TypeParser -{ - return (_: CmdArgument, args: ArgTokenStream): number => { - const arg = args.current; - args.consume(); - const val = parseInt(arg); - if (isNaN(val)) - throw 'invalid integer: ' + arg; - return val; - }; -} - -export function variadic(fn: TypeParser): TypeParser -{ - return (def: CmdArgument, args: ArgTokenStream): T[] => { - const vals = []; - while (!args.isEnd) - vals.push(fn(def, args)); - return vals; - }; -} - -export function strenum(vals: T[]): TypeParser -{ - return (_: CmdArgument, args: ArgTokenStream): T => { - const arg = args.current as T; - args.consume(); - if (!vals.includes(arg)) - throw 'expected any of: ' + vals.join(', ') + '. got: ' + arg; - return arg; - }; -}