diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-21 20:56:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-21 20:56:19 +0000 |
commit | 0b6210cd37b68b94252cb798598b12974a20e1c1 (patch) | |
tree | e371686554a877842d95aa94f100bee552ff2a8e /llparse-builder/src/node | |
parent | Initial commit. (diff) | |
download | node-undici-0b6210cd37b68b94252cb798598b12974a20e1c1.tar.xz node-undici-0b6210cd37b68b94252cb798598b12974a20e1c1.zip |
Adding upstream version 5.28.2+dfsg1+~cs23.11.12.3.upstream/5.28.2+dfsg1+_cs23.11.12.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'llparse-builder/src/node')
-rw-r--r-- | llparse-builder/src/node/base.ts | 96 | ||||
-rw-r--r-- | llparse-builder/src/node/consume.ts | 19 | ||||
-rw-r--r-- | llparse-builder/src/node/error.ts | 24 | ||||
-rw-r--r-- | llparse-builder/src/node/index.ts | 8 | ||||
-rw-r--r-- | llparse-builder/src/node/invoke.ts | 39 | ||||
-rw-r--r-- | llparse-builder/src/node/match.ts | 162 | ||||
-rw-r--r-- | llparse-builder/src/node/pause.ts | 25 | ||||
-rw-r--r-- | llparse-builder/src/node/span-end.ts | 19 | ||||
-rw-r--r-- | llparse-builder/src/node/span-start.ts | 16 |
9 files changed, 408 insertions, 0 deletions
diff --git a/llparse-builder/src/node/base.ts b/llparse-builder/src/node/base.ts new file mode 100644 index 0000000..9840f16 --- /dev/null +++ b/llparse-builder/src/node/base.ts @@ -0,0 +1,96 @@ +import * as assert from 'assert'; +import binarySearch = require('binary-search'); +import { Edge } from '../edge'; + +/** + * Base class for all graph nodes. + */ +export abstract class Node { + private otherwiseEdge: Edge | undefined; + private privEdges: Edge[] = []; + + /** + * @param name Node name + */ + constructor(public readonly name: string) { + // no-op + } + + /** + * Create an otherwise edge to node `node`. + * + * This edge is executed when no other edges match current input. No + * characters are consumed upon transition. + * + * NOTE: At most one otherwise (skipping or not) edge can be set, most nodes + * except `Error` require it. + * + * @param node Target node + */ + public otherwise(node: Node): this { + if (this.otherwiseEdge !== undefined) { + throw new Error('Node already has `otherwise` or `skipTo`'); + } + + this.otherwiseEdge = new Edge(node, true, undefined, undefined); + return this; + } + + /** + * Create a skipping otherwise edge to node `node`. + * + * This edge is executed when no other edges match current input. Single + * character is consumed upon transition. + * + * NOTE: At most one otherwise (skipping or not) edge can be set, most nodes + * except `Error` require it. + * + * @param node Target node + */ + public skipTo(node: Node): this { + if (this.otherwiseEdge !== undefined) { + throw new Error('Node already has `otherwise` or `skipTo`'); + } + + this.otherwiseEdge = new Edge(node, false, undefined, undefined); + return this; + } + + // Limited public use + + /** Get otherwise edge. */ + public getOtherwiseEdge(): Edge | undefined { + return this.otherwiseEdge; + } + + /** Get list of all non-otherwise edges. */ + public getEdges(): ReadonlyArray<Edge> { + return this.privEdges; + } + + /** Get list of all edges (including otherwise, if present). */ + public getAllEdges(): ReadonlyArray<Edge> { + const res = this.privEdges; + if (this.otherwiseEdge === undefined) { + return res; + } else { + return res.concat(this.otherwiseEdge); + } + } + + /** Get iterator through all non-otherwise edges. */ + public *[Symbol.iterator](): Iterator<Edge> { + yield* this.privEdges; + } + + // Internal + + protected addEdge(edge: Edge): void { + assert.notStrictEqual(edge.key, undefined); + + const index = binarySearch(this.privEdges, edge, Edge.compare); + assert(index < 0, 'Attempting to create duplicate edge'); + + this.privEdges.splice(-1 - index, 0, edge); + } +} diff --git a/llparse-builder/src/node/consume.ts b/llparse-builder/src/node/consume.ts new file mode 100644 index 0000000..eff4037 --- /dev/null +++ b/llparse-builder/src/node/consume.ts @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This node consumes number of characters specified by state's property with + * name `field` from the input, and forwards execution to `otherwise` node. + */ +export class Consume extends Node { + /** + * @param field State's property name + */ + constructor(public readonly field: string) { + super('consume_' + field); + + if (/^_/.test(field)) { + throw new Error(`Can't use internal field in \`consume()\`: "${field}"`); + } + } +} diff --git a/llparse-builder/src/node/error.ts b/llparse-builder/src/node/error.ts new file mode 100644 index 0000000..393f566 --- /dev/null +++ b/llparse-builder/src/node/error.ts @@ -0,0 +1,24 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This node terminates the execution with an error + */ +class NodeError extends Node { + /** + * @param code Error code to return to user + * @param reason Error description to store in parser's state + */ + constructor(public readonly code: number, public readonly reason: string) { + super('error'); + assert.strictEqual(code, code | 0, 'code must be integer'); + } + + /** `.otherwise()` is not supported on this type of node */ + public otherwise(node: Node): this { throw new Error('Not supported'); } + + /** `.skipTo()` is not supported on this type of node */ + public skipTo(node: Node): this { throw new Error('Not supported'); } +} + +export { NodeError as Error }; diff --git a/llparse-builder/src/node/index.ts b/llparse-builder/src/node/index.ts new file mode 100644 index 0000000..e3d5fe5 --- /dev/null +++ b/llparse-builder/src/node/index.ts @@ -0,0 +1,8 @@ +export { Node } from './base'; +export { Consume } from './consume'; +export { Error } from './error'; +export { Invoke, IInvokeMap } from './invoke'; +export { Match } from './match'; +export { Pause } from './pause'; +export { SpanStart } from './span-start'; +export { SpanEnd } from './span-end'; diff --git a/llparse-builder/src/node/invoke.ts b/llparse-builder/src/node/invoke.ts new file mode 100644 index 0000000..d6791a7 --- /dev/null +++ b/llparse-builder/src/node/invoke.ts @@ -0,0 +1,39 @@ +import * as assert from 'assert'; + +import { Code } from '../code'; +import { Edge } from '../edge'; +import { Node } from './base'; + +/** + * Map of return codes of the callback. Each key is a return code, + * value is the target node that must be executed upon getting such return code. + */ +export interface IInvokeMap { + readonly [key: number]: Node; +} + +/** + * This node invokes either external callback or intrinsic code and passes the + * execution to either a target from a `map` (if the return code matches one of + * registered in it), or to `otherwise` node. + */ +export class Invoke extends Node { + /** + * @param code External callback or intrinsic code. Can be created with + * `builder.code.*()` methods. + * @param map Map from callback return codes to target nodes + */ + constructor(public readonly code: Code, map: IInvokeMap) { + super('invoke_' + code.name); + + Object.keys(map).forEach((mapKey) => { + const numKey: number = parseInt(mapKey, 10); + const targetNode = map[numKey]!; + + assert.strictEqual(numKey, numKey | 0, + 'Invoke\'s map keys must be integers'); + + this.addEdge(new Edge(targetNode, true, numKey, undefined)); + }); + } +} 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; + } +} diff --git a/llparse-builder/src/node/pause.ts b/llparse-builder/src/node/pause.ts new file mode 100644 index 0000000..2dcf5d1 --- /dev/null +++ b/llparse-builder/src/node/pause.ts @@ -0,0 +1,25 @@ +import * as assert from 'assert'; +import { Node } from './base'; + +/** + * This returns the specified error code, but makes the resumption to + * `otherwise` target possible. + */ +export class Pause extends Node { + /** + * @param code Error code to return + * @param reason Error description + */ + constructor(public readonly code: number, public readonly reason: string) { + super('pause'); + assert.strictEqual(code, code | 0, 'code must be integer'); + } + + /** + * `.skipTo()` is not supported on this type of node, please use + * `.otherwise()` + */ + public skipTo(node: Node): this { + throw new Error('Not supported, please use `pause.otherwise()`'); + } +} diff --git a/llparse-builder/src/node/span-end.ts b/llparse-builder/src/node/span-end.ts new file mode 100644 index 0000000..377cd73 --- /dev/null +++ b/llparse-builder/src/node/span-end.ts @@ -0,0 +1,19 @@ +import { Span } from '../span'; +import { Node } from './base'; + +/** + * Indicates span end. + * + * A callback will be invoked with all input data since the most recent of: + * + * * Span start invocation + * * Parser execution + */ +export class SpanEnd extends Node { + /** + * @param span Span instance + */ + constructor(public readonly span: Span) { + super(`span_end_${span.callback.name}`); + } +} diff --git a/llparse-builder/src/node/span-start.ts b/llparse-builder/src/node/span-start.ts new file mode 100644 index 0000000..f81b432 --- /dev/null +++ b/llparse-builder/src/node/span-start.ts @@ -0,0 +1,16 @@ +import { Span } from '../span'; +import { Node } from './base'; + +/** + * Indicates span start. + * + * See `SpanEnd` for details on callback invocation. + */ +export class SpanStart extends Node { + /** + * @param span Span instance + */ + constructor(public readonly span: Span) { + super(`span_start_${span.callback.name}`); + } +} |