UNPKG

asn1tools-js

Version:

ASN.1 encoding and decoding library for TypeScript/JavaScript, compatible with Python asn1tools

450 lines 13.7 kB
"use strict"; /** * ASN.1 grammar parser */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Asn1Parser = void 0; const types_1 = require("./types"); class Asn1Parser { constructor() { this.input = ''; this.position = 0; this.line = 1; this.column = 1; } parse(content) { this.input = content; this.position = 0; this.line = 1; this.column = 1; const modules = []; this.skipWhitespaceAndComments(); while (!this.isAtEnd()) { const module = this.parseModule(); modules.push(module); this.skipWhitespaceAndComments(); } return modules; } parseModule() { // Parse module header: ModuleName DEFINITIONS ::= BEGIN const moduleName = this.parseIdentifier(); this.expectKeyword('DEFINITIONS'); this.expectToken('::='); this.expectKeyword('BEGIN'); const types = new Map(); this.skipWhitespaceAndComments(); while (!this.checkKeyword('END') && !this.isAtEnd()) { this.skipWhitespaceAndComments(); const parsedType = this.parseTypeAssignment(); types.set(parsedType.name, parsedType); } this.expectKeyword('END'); return { name: moduleName, types }; } parseTypeAssignment() { const name = this.parseIdentifier(); this.expectToken('::='); const type = this.parseType(); return { ...type, name }; } parseType() { this.skipWhitespaceAndComments(); let type; if (this.checkKeyword('INTEGER')) { type = this.parseIntegerType(); } else if (this.checkKeyword('BOOLEAN')) { type = this.parseBooleanType(); } else if (this.checkKeyword('OCTET')) { type = this.parseOctetStringType(); } else if (this.checkKeyword('SEQUENCE')) { // Peek ahead to decide between "SEQUENCE {...}" and "SEQUENCE OF <Type>" const savedPos = this.position; const savedLine = this.line; const savedCol = this.column; // Consume the keyword 'SEQUENCE' this.expectKeyword('SEQUENCE'); this.skipWhitespaceAndComments(); if (this.checkKeyword('OF')) { // It's a SEQUENCE OF construct this.expectKeyword('OF'); const elementType = this.parseType(); type = { name: '', type: 'SEQUENCE_OF', elementType }; } else { // Not a SEQUENCE OF – rewind and parse as a normal SEQUENCE this.position = savedPos; this.line = savedLine; this.column = savedCol; type = this.parseSequenceType(); } } else if (this.checkKeyword('CHOICE')) { type = this.parseChoiceType(); } else if (this.checkKeyword('ENUMERATED')) { type = this.parseEnumeratedType(); } else if (this.checkKeyword('NULL')) { type = this.parseNullType(); } else { // Handle defined types (references to other types) this.skipWhitespaceAndComments(); if (!this.isAlpha(this.peek())) { this.error('Expected type reference (identifier)'); } const typeName = this.parseIdentifier(); type = { name: '', type: 'DEFINED', constraints: { definedType: typeName } }; } // After parsing any type, check for constraints this.skipWhitespaceAndComments(); if (this.check('(')) { const constraints = this.parseConstraints(); type = { ...type, constraints: { ...(type.constraints || {}), ...constraints } }; } return type; } parseIntegerType() { this.expectKeyword('INTEGER'); return { name: '', type: 'INTEGER' }; } parseBooleanType() { this.expectKeyword('BOOLEAN'); return { name: '', type: 'BOOLEAN' }; } parseOctetStringType() { this.expectKeyword('OCTET'); this.skipWhitespaceAndComments(); this.expectKeyword('STRING'); return { name: '', type: 'OCTET_STRING' }; } parseNullType() { this.expectKeyword('NULL'); return { name: '', type: 'NULL' }; } parseSequenceType() { this.expectKeyword('SEQUENCE'); this.expectToken('{'); const members = []; while (true) { this.skipWhitespaceAndComments(); if (this.check('}') || this.isAtEnd()) { break; } const member = this.parseSequenceMember(); members.push(member); this.skipWhitespaceAndComments(); if (this.check(',')) { this.expectToken(','); } } this.expectToken('}'); return { name: '', type: 'SEQUENCE', members }; } parseSequenceMember() { const name = this.parseIdentifier(); let tag; this.skipWhitespaceAndComments(); if (this.check('[')) { tag = this.parseTag(); this.skipWhitespaceAndComments(); } let type = this.parseType(); let optional = false; let defaultValue; this.skipWhitespaceAndComments(); if (this.checkKeyword('OPTIONAL')) { this.expectKeyword('OPTIONAL'); optional = true; } else if (this.checkKeyword('DEFAULT')) { this.expectKeyword('DEFAULT'); defaultValue = this.parseValue(); } return { ...type, name, tag, optional, default: defaultValue }; } parseChoiceType() { this.expectKeyword('CHOICE'); this.expectToken('{'); const choices = []; while (true) { this.skipWhitespaceAndComments(); if (this.check('}') || this.isAtEnd()) { break; } const choice = this.parseChoiceAlternative(); choices.push(choice); this.skipWhitespaceAndComments(); if (this.check(',')) { this.expectToken(','); } } this.expectToken('}'); return { name: '', type: 'CHOICE', choices }; } parseChoiceAlternative() { const name = this.parseIdentifier(); let tag; this.skipWhitespaceAndComments(); if (this.check('[')) { tag = this.parseTag(); this.skipWhitespaceAndComments(); } let type = this.parseType(); return { ...type, name, tag }; } parseEnumeratedType() { this.expectKeyword('ENUMERATED'); this.expectToken('{'); const values = []; let autoValue = 0; this.skipWhitespaceAndComments(); while (!this.check('}') && !this.isAtEnd()) { const name = this.parseIdentifier(); let value = autoValue; if (this.check('(')) { this.expectToken('('); value = this.parseNumber(); this.expectToken(')'); } values.push([name, value]); autoValue = value + 1; if (this.check(',')) { this.expectToken(','); } } this.expectToken('}'); return { name: '', type: 'ENUMERATED', constraints: { values } }; } parseTag() { this.expectToken('['); const tagNumber = this.parseNumber(); this.expectToken(']'); return tagNumber; } parseConstraints() { this.expectToken('('); this.skipWhitespaceAndComments(); const constraints = {}; if (this.checkKeyword('SIZE')) { this.expectKeyword('SIZE'); this.expectToken('('); const size = this.parseNumber(); this.expectToken(')'); constraints.size = size; } else { // Range constraint const min = this.parseNumber(); if (this.check('.')) { this.expectToken('.'); this.expectToken('.'); const max = this.parseNumber(); constraints.range = [min, max]; } else { constraints.value = min; } } this.expectToken(')'); return constraints; } parseValue() { this.skipWhitespaceAndComments(); if (this.checkNumber()) { return this.parseNumber(); } if (this.check('"')) { return this.parseString(); } if (this.checkKeyword('TRUE')) { this.expectKeyword('TRUE'); return true; } if (this.checkKeyword('FALSE')) { this.expectKeyword('FALSE'); return false; } if (this.checkKeyword('NULL')) { this.expectKeyword('NULL'); return null; } return this.parseIdentifier(); } parseIdentifier() { this.skipWhitespaceAndComments(); if (!this.isAlpha(this.peek())) { this.error('Expected identifier'); } const start = this.position; while (this.isAlphaNumeric(this.peek())) { this.advance(); } return this.input.substring(start, this.position); } parseNumber() { this.skipWhitespaceAndComments(); let negative = false; if (this.check('-')) { negative = true; this.advance(); } if (!this.isDigit(this.peek())) { this.error('Expected number'); } const start = this.position; while (this.isDigit(this.peek())) { this.advance(); } const value = parseInt(this.input.substring(start, this.position), 10); return negative ? -value : value; } parseString() { this.expectToken('"'); const start = this.position; while (!this.check('"') && !this.isAtEnd()) { this.advance(); } const value = this.input.substring(start, this.position); this.expectToken('"'); return value; } skipWhitespaceAndComments() { while (true) { const char = this.peek(); if (char === ' ' || char === '\t' || char === '\r') { this.advance(); } else if (char === '\n') { this.line++; this.column = 1; this.advance(); } else if (char === '-' && this.peekNext() === '-') { // Skip line comment while (!this.isAtEnd() && this.peek() !== '\n') { this.advance(); } } else { break; } } } checkKeyword(keyword) { if (this.position + keyword.length > this.input.length) { return false; } const slice = this.input.substring(this.position, this.position + keyword.length); const nextChar = this.position + keyword.length < this.input.length ? this.input[this.position + keyword.length] : ' '; return slice === keyword && !this.isAlphaNumeric(nextChar || ' '); } expectKeyword(keyword) { this.skipWhitespaceAndComments(); if (!this.checkKeyword(keyword)) { this.error(`Expected keyword '${keyword}'`); } this.position += keyword.length; } expectToken(token) { this.skipWhitespaceAndComments(); if (!this.check(token)) { this.error(`Expected '${token}'`); } this.position += token.length; } check(expected) { if (this.position + expected.length > this.input.length) { return false; } return this.input.substring(this.position, this.position + expected.length) === expected; } checkNumber() { return this.isDigit(this.peek()) || (this.peek() === '-' && this.isDigit(this.peekNext())); } advance() { if (!this.isAtEnd()) { this.column++; return this.input[this.position++] || '\0'; } return '\0'; } peek() { if (this.isAtEnd()) return '\0'; return this.input[this.position] || '\0'; } peekNext() { if (this.position + 1 >= this.input.length) return '\0'; return this.input[this.position + 1] || '\0'; } isAtEnd() { return this.position >= this.input.length; } isAlpha(char) { return /[a-zA-Z]/.test(char); } isDigit(char) { return /[0-9]/.test(char); } isAlphaNumeric(char) { return this.isAlpha(char) || this.isDigit(char); } error(message) { throw new types_1.ParseError(`${message} at line ${this.line}, column ${this.column}`); } } exports.Asn1Parser = Asn1Parser; //# sourceMappingURL=parser.js.map