UNPKG

jspurefix

Version:
183 lines (163 loc) 5.98 kB
import { FixDefinitions } from '../../dictionary/definition' import { Ascii } from '../ascii' import { AsciiView } from './ascii-view' import { AsciiSegmentParser } from './ascii-segment-parser' import { AsciiParserState, ParseState } from './ascii-parser-state' import { Structure } from '../structure' import { Tags } from '../tags' import { MsgParser } from '../msg-parser' import { MsgView } from '../msg-view' import { Readable } from 'stream' import { ElasticBuffer } from '../elastic-buffer' import { SegmentDescription, SegmentType } from '../segment-description' export class AsciiParser extends MsgParser { private static nextId: number = 0 public readonly id: number public readonly state: AsciiParserState private readonly receivingBuffer: ElasticBuffer private readonly segmentParser: AsciiSegmentParser // allocate enough in receive buffer so buffer does not constant resize back after large messages // want to keep one slice of memory and constantly reuse it constructor (public readonly definitions: FixDefinitions, public readonly readStream: Readable, public readonly delimiter: number, public readonly writeDelimiter: number = delimiter, public readonly maxMessageLen: number = 160 * 1024) { super() this.id = AsciiParser.nextId++ this.segmentParser = new AsciiSegmentParser(definitions) this.receivingBuffer = new ElasticBuffer(maxMessageLen) this.state = new AsciiParserState(this.receivingBuffer) this.state.locations = new Tags(definitions, maxMessageLen / 10) this.state.beginMessage() this.subscribe() } private subscribe (): void { const inst: AsciiParser = this const Writable = require('stream').Writable const stream = this.readStream const receiver = new Writable({ write: (data: Buffer, _: any, done: Function) => { try { inst.parse(data, data.length) done() } catch (e) { done(e) } } }) receiver.on('error', (e: Error) => { this.emit('error', e) }) // receive from say a socket or file and pipe to parser which discovers messages stream.pipe(receiver).on('finish', () => { this.emit('done') }) stream.on('error', (e) => { this.emit('error', e) }) stream.on('end', () => { this.emit('end') }) } private msg (ptr: number): void { const receivingBuffer: ElasticBuffer = this.receivingBuffer const state = this.state if (this.listeners('decoded')) { this.emit('decoded', state.msgType, receivingBuffer, ptr) } this.emit('msg', state.msgType, this.getView(ptr)) state.beginMessage() } private parse (readBuffer: Buffer, end: number): void { const state = this.state const eq: number = Ascii.Eq const zero: number = Ascii.Zero const nine: number = Ascii.Nine const delimiter: number = this.delimiter const writeDelimiter: number = this.writeDelimiter const receivingBuffer: ElasticBuffer = this.receivingBuffer const switchDelimiter = writeDelimiter !== delimiter let readPtr: number = 0 while (readPtr < end) { let charAtPos: number = readBuffer[readPtr] const writePtr = receivingBuffer.saveChar(charAtPos) - 1 switch (state.parseState) { case ParseState.MsgComplete: { this.msg(writePtr) continue } case ParseState.BeginField: { const atDigit: boolean = charAtPos >= zero && charAtPos <= nine if (atDigit) { state.beginTag(writePtr) } break } case ParseState.ParsingTag: { const isEquals: boolean = charAtPos === eq if (isEquals) { state.endTag(writePtr) } break } case ParseState.ParsingRawData: { // keep skipping until length read, regardless of delimiter or not if (state.incRaw()) { // having consumed the raw field expecting delimiter if (charAtPos === delimiter) { if (switchDelimiter) { receivingBuffer.switchChar(writeDelimiter) } state.store() } else { throw new Error(`delimiter (${delimiter}) expected at position ${readPtr} when value is ${charAtPos}`) } } break } case ParseState.ParsingRawDataLength: case ParseState.ParsingValue: { if (charAtPos === delimiter) { if (switchDelimiter) { receivingBuffer.switchChar(writeDelimiter) } state.store() } break } default: { throw new Error(`fix parser in unknown state ${state}`) } } readPtr++ } switch (state.parseState) { case ParseState.MsgComplete: { this.msg(receivingBuffer.getPos()) break } } } private getView (ptr: number): MsgView { const state = this.state const locations = state.locations const source = this.receivingBuffer const delimiter = this.delimiter const replace = this.writeDelimiter if (state.message) { const structure: Structure = this.segmentParser.parse(state.msgType, locations, locations.nextTagPos - 1) return new AsciiView(structure.msg(), source, structure, ptr, delimiter, replace) } const structure = new Structure(locations, []) const segment = new SegmentDescription('unknown', locations.tagPos[0].tag, null, 0, 1, SegmentType.Unknown) segment.endPosition = locations.nextTagPos - 1 return new AsciiView(segment, source, structure, ptr, delimiter, replace) } }