Richer paramater parsing.

This commit is NOT contributed under the Apache-2.0 License.
Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
All rights reserved.
This commit is contained in:
gnuxie
2022-12-28 17:58:45 +00:00
parent 66907a571e
commit e6d0aaf9ea
2 changed files with 55 additions and 26 deletions
@@ -30,7 +30,7 @@ limitations under the License.
*/
import { ReadItem } from "./CommandReader";
import { ParamaterParser, ArgumentStream } from "./ParamaterParsing";
import { ParamaterParser, ArgumentStream, IArgumentListParser } from "./ParamaterParsing";
import { CommandResult } from "./Validation";
/**
@@ -112,7 +112,7 @@ export function findCommandTable<ExecutorType extends BaseFunction>(name: string
export class InterfaceCommand<ExecutorType extends BaseFunction> {
constructor(
private readonly paramaterParser: ParamaterParser,
private readonly argumentListParser: IArgumentListParser,
private readonly command: ExecutorType,
public readonly designator: string[],
) {
@@ -122,7 +122,7 @@ export class InterfaceCommand<ExecutorType extends BaseFunction> {
// probably... it's just that means that invoke has to return the validation result lol.
// Though this makes no sense if parsing is part of finding a matching command.
public parseArguments(...args: ReadItem[]): ReturnType<ParamaterParser> {
return this.paramaterParser(...args);
return this.argumentListParser.parseFunction(...args);
}
public invoke(context: ThisParameterType<ExecutorType>, ...args: Parameters<ExecutorType>): ReturnType<ExecutorType> {
@@ -130,7 +130,7 @@ export class InterfaceCommand<ExecutorType extends BaseFunction> {
}
public async parseThenInvoke(context: ThisParameterType<ExecutorType>, ...items: ReadItem[]): Promise<ReturnType<ExecutorType>> {
const paramaterDescription = this.paramaterParser(...items);
const paramaterDescription = this.argumentListParser.parseFunction(...items);
if (paramaterDescription.isErr()) {
// The inner type is irrelevant when it is Err, i don't know how to encode this in TS's type system but whatever.
return paramaterDescription as ReturnType<Awaited<ExecutorType>>;
@@ -140,7 +140,7 @@ export class InterfaceCommand<ExecutorType extends BaseFunction> {
}
export function defineInterfaceCommand<ExecutorType extends BaseFunction>(description: {
paramaters: ParamaterParser,
paramaters: IArgumentListParser,
table: string,
command: ExecutorType,
designator: string[],
@@ -172,30 +172,59 @@ export interface ParamaterDescription {
export type ParamaterParser = (...readItems: ReadItem[]) => CommandResult<ParsedArguments>;
export function paramaters(descriptions: ParamaterDescription[], restParser: undefined|RestParser = undefined): ParamaterParser {
return (...readItems: ReadItem[]) => {
const itemStream = new ArgumentStream(readItems);
for (const paramater of descriptions) {
if (itemStream.peekItem() === undefined) {
// FIXME asap: we need a proper paramater description?
return CommandError.Result('expected an argument', `An argument for the paramater ${paramater.name} was expected but was not provided.`);
// So this should really just be something used by defineInterfaceCommand which turns paramaters into a validator that can be used.
// It can't be, because then otherwise how does the semantics for union work?
// We should have a new type of CommandResult that accepts a ParamterDescription, and can render what's wrong (e.g. missing paramater).
// Showing where in the item stream it is missing and the command syntax and everything lovely like that.
// How does that work with Union?
export function paramaters(descriptions: ParamaterDescription[], restParser: undefined|RestParser = undefined): IArgumentListParser {
return new ArgumentListParser(descriptions, restParser);
}
export interface IArgumentListParser {
readonly parseFunction: ParamaterParser,
readonly descriptions: ParamaterDescription[],
readonly restParser?: RestParser,
}
/**
* Zis is le argument list parser
* It is used directly by InterfaceCommand to consume, parse, validate le arguments.
*/
class ArgumentListParser implements IArgumentListParser {
public readonly parseFunction: ParamaterParser
constructor(
public readonly descriptions: ParamaterDescription[],
public readonly restParser: undefined|RestParser = undefined,
) {
this.parseFunction = this.makeParseFunction(descriptions, restParser);
}
private makeParseFunction(descriptions: ParamaterDescription[], restParser: undefined|RestParser): ParamaterParser {
return (...readItems: ReadItem[]) => {
const itemStream = new ArgumentStream(readItems);
for (const paramater of descriptions) {
if (itemStream.peekItem() === undefined) {
// FIXME asap: we need a proper paramater description?
return CommandError.Result('expected an argument', `An argument for the paramater ${paramater.name} was expected but was not provided.`);
}
const item = itemStream.readItem()!;
const result = paramater.acceptor.validator(item);
if (result.err) {
// should really allow the help to be printed later on and keep the whole context?
return CommandResult.Err(result.err);
}
}
const item = itemStream.readItem()!;
const result = paramater.acceptor.validator(item);
if (result.err) {
// should really allow the help to be printed later on and keep the whole context?
return CommandResult.Err(result.err);
if (restParser) {
const result = restParser.parseRest(itemStream);
if (result.isErr()) {
return CommandResult.Err(result.err);
}
return CommandResult.Ok({ immediateArguments: readItems, rest: result.ok });
} else {
return CommandResult.Ok({ immediateArguments: readItems });
}
}
if (restParser) {
const result = restParser.parseRest(itemStream);
if (result.isErr()) {
return CommandResult.Err(result.err);
}
return CommandResult.Ok({ immediateArguments: readItems, rest: result.ok });
} else {
return CommandResult.Ok({ immediateArguments: readItems });
}
}
}