diff options
Diffstat (limited to '')
-rw-r--r-- | llparse-builder/src/node/match.ts | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/llparse-builder/src/node/match.ts b/llparse-builder/src/node/match.ts new file mode 100644 index 0000000..617a659 --- /dev/null +++ b/llparse-builder/src/node/match.ts @@ -0,0 +1,162 @@ +import * as assert from 'assert'; +import { Buffer } from 'buffer'; + +import { Edge } from '../edge'; +import { Transform } from '../transform'; +import { toBuffer } from '../utils'; +import { Node } from './base'; + +/** + * Character/sequence to match. + * + * May have following types: + * + * * `number` - for single character + * * `string` - for printable character sequence + * * `Buffer` - for raw byte sequence + */ +export type MatchSingleValue = string | number | Buffer; + +/** + * Convenience type for passing several characters/sequences to match methods. + */ +export type MatchValue = MatchSingleValue | ReadonlyArray<MatchSingleValue>; + +/** + * A map from characters/sequences to `.select()`'s values. Used for specifying + * the value to be passed to `.select()'`s targets. + */ +export interface IMatchSelect { + readonly [key: string]: number; +} + +/** + * This node matches characters/sequences and forwards the execution according + * to matched character with optional attached value (See `.select()`). + */ +export class Match extends Node { + private transformFn: Transform | undefined; + + /** + * Set character transformation function. + * + * @param transform Transformation to apply. Can be created with + * `builder.transform.*()` methods. + */ + public transform(transformFn: Transform): this { + this.transformFn = transformFn; + return this; + } + + /** + * Match sequence/character and forward execution to `next` on success, + * consuming matched bytes of the input. + * + * No value is attached on such execution forwarding, and the target node + * **must not** be an `Invoke` node with a callback expecting the value. + * + * @param value Sequence/character to be matched + * @param next Target node to be executed on success. + */ + public match(value: MatchValue, next: Node): this { + if (Array.isArray(value)) { + for (const subvalue of value) { + this.match(subvalue, next); + } + return this; + } + + const buffer = toBuffer(value as MatchSingleValue); + const edge = new Edge(next, false, buffer, undefined); + this.addEdge(edge); + return this; + } + + /** + * Match character and forward execution to `next` on success + * without consuming one byte of the input. + * + * No value is attached on such execution forwarding, and the target node + * **must not** be an `Invoke` with a callback expecting the value. + * + * @param value Character to be matched + * @param next Target node to be executed on success. + */ + public peek(value: MatchValue, next: Node): this { + if (Array.isArray(value)) { + for (const subvalue of value) { + this.peek(subvalue, next); + } + return this; + } + + const buffer = toBuffer(value as MatchSingleValue); + assert.strictEqual(buffer.length, 1, + '`.peek()` accepts only single character keys'); + + const edge = new Edge(next, true, buffer, undefined); + this.addEdge(edge); + return this; + } + + /** + * Match character/sequence and forward execution to `next` on success + * consumed matched bytes of the input. + * + * Value is attached on such execution forwarding, and the target node + * **must** be an `Invoke` with a callback expecting the value. + * + * Possible signatures: + * + * * `.select(key, value [, next ])` + * * `.select({ key: value } [, next])` + * + * @param keyOrMap Either a sequence to match, or a map from sequences to + * values + * @param valueOrNext Either an integer value to be forwarded to the target + * node, or an otherwise node + * @param next Convenience param. Same as calling `.otherwise(...)` + */ + public select(keyOrMap: MatchSingleValue | IMatchSelect, + valueOrNext?: number | Node, next?: Node): this { + // .select({ key: value, ... }, next) + if (typeof keyOrMap === 'object') { + assert(valueOrNext instanceof Node, + 'Invalid `next` argument of `.select()`'); + assert.strictEqual(next, undefined, + 'Invalid argument count of `.select()`'); + + const map: IMatchSelect = keyOrMap as IMatchSelect; + next = valueOrNext as Node | undefined; + + Object.keys(map).forEach((mapKey) => { + const numKey: number = mapKey as any; + + this.select(numKey, map[numKey]!, next); + }); + return this; + } + + // .select(key, value, next) + assert.strictEqual(typeof valueOrNext, 'number', + 'Invalid `value` argument of `.select()`'); + assert.notStrictEqual(next, undefined, + 'Invalid `next` argument of `.select()`'); + + const key = toBuffer(keyOrMap as MatchSingleValue); + const value = valueOrNext as number; + + const edge = new Edge(next!, false, key, value); + this.addEdge(edge); + return this; + } + + // Limited public use + + /** + * Get tranformation function + */ + public getTransform(): Transform | undefined { + return this.transformFn; + } +} |