@mrhiden/cstruct
Version:
For packing and unpacking bytes (C like structures) in/from Buffer based on Object/Array type for parsing.
272 lines (271 loc) • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelParser = void 0;
class ModelParser {
static checkWhetherSizeIsNumber(size) {
return !Number.isNaN(+size);
}
static checkSize(size) {
size = size.toLowerCase();
if (!this._allowedLengthTypes.includes(size) && !this.checkWhetherSizeIsNumber(size)) {
throw Error(`Unsupported size "${size}".`);
}
return size;
}
static translateType(type) {
const typeLowerCase = type.toLowerCase();
if (this._sTypes.includes(typeLowerCase)) {
return 's';
}
if (this._wsTypes.includes(typeLowerCase)) {
return 'ws';
}
if (this._bufTypes.includes(typeLowerCase)) {
return 'buf';
}
if (this._jTypes.includes(typeLowerCase)) {
return 'j';
}
return type;
}
static getSize(size) {
const value = +size;
return {
isNumber: !Number.isNaN(value),
staticSize: value
};
}
static removeComments(json) {
return json
.split('\n')
.map(line => {
const commentIndex = line.indexOf('//');
if (commentIndex === -1) {
return line;
}
return line.slice(0, commentIndex);
})
.join('\n');
}
static prepareJson(json) {
json = this.removeComments(json); // remove comments
json = json.replace(/^\s+$/m, ``); // remove empty lines
json = json.replace(/\n/g, ``); // remove line breaks
json = json.trim();
json = json.replace(/['"]/g, ``); // remove all `'"`
json = json.replace(/\s*([,:;{}[\]])\s*/g, `$1`); // remove spaces around `,:;{}[]`
json = json.replace(/\s{2,}/g, ` `); // reduce spaces '\s'x to one ' '
return json;
}
static arrayOfItems1(json) {
// Static `itemType[size]`
// `Abc[2]` => `[Abc,Abc]`
const matches = json.match(/^\w+\[\w+]$/g) ?? [];
for (const match of matches) {
const groups = match.match(/^(?<type>\w+)\[(?<size>\w+)]$/)?.groups;
const { size, type } = groups;
const { isNumber, staticSize } = this.getSize(size);
if (isNumber) {
const replacer = `[${Array(staticSize).fill(type).join(',')}]`;
json = json.split(match).join(replacer);
}
else {
throw Error(`Unsupported size "${size}".`);
}
}
return json;
}
static arrayOfItems2(json) {
// Static [itemType/size]
// `[Abc/2]` `[Abc,Abc]`
const matches = json.match(/\[\w+\/\w+]/g) ?? [];
for (const match of matches) {
const groups = match.match(/\[(?<type>\w+)\/(?<size>\w+)]/)?.groups;
const { size, type } = groups;
const { isNumber, staticSize } = this.getSize(size);
if (isNumber) {
const replacer = `[${Array(staticSize).fill(type).join(',')}]`;
json = json.split(match).join(replacer);
}
else {
throw Error(`Unsupported size "${size}".`);
}
}
return json;
}
static staticOrDynamicArray(json) {
// Static
// `{some:s[2]}` => `{some: s2}`
// `{some:string[2]}` => `{some: s2}`
// `{some:Abc[i8]}` => `{some: [Abc,Abc]}`
// `{some:buf[2]}` => `{some: buf2}`
// `{some:buffer[2]}` => `{some: buf2}`
// `{some:j[2]}` => `{some: j2}`
// `{some:json[2]}` => `{some: j2}`
// `{some:any[2]}` => `{some: j2}`
// Dynamic
// `{some:s[i8]}` => `{some.i8: s}`
// `{some:string[i8]}` => `{some.i8: s}`
// `{some:Abc[i8]}` => `{some.i8: Abc}`
// `{some:buf[i8]}` => `{some.i8: buf}`
// `{some:buffer[i8]}` => `{some.i8: buf}`
// `{some:j[i8]}` => `{some.i8: j}`
// `{some:json[i8]}` => `{some.i8: j}`
// `{some:any[i8]}` => `{some.i8: j}`
const matches = json.match(/\w+:\w+\[\w+]/g) ??
[];
for (const match of matches) {
const groups = match.match(/(?<key>\w+):?(?<type>\w+)\[(?<size>\w+)];?/)?.groups;
const { key, size, type } = groups;
const sizeLowerCase = this.checkSize(size);
const translatedType = this.translateType(type);
const replacer = `${key}.${sizeLowerCase}:${translatedType}`;
json = json.split(match).join(replacer);
}
return json;
}
static justArray(json) {
// Static
// `s[2]` => `s.2`
// `string[2]` => `s.2`
// `Abc[2]` => `Abc.2`
// `buf[2]` => `buf.2`
// `buffer[2]` => `buf.2`
// `i16[2]` => `i16.2`
// `j[2]` => `j.2`
// `json[2]` => `j.2`
// `any[2]` => `j.2`
// Dynamic
// `s[i8]` => `s.i8`
// `string[i8]` => `s.i8`
// `Abc[i8]` => `Abc.i8`
// `buf[i8]` => `buf.i8`
// `buffer[i8]` => `buf.i8`
// `i16[i8]` => `i16.i8`
// `j[i8]` => `j.i8`
// `json[i8]` => `j.i8`
// `any[i8]` => `j.i8`
const matches = json.match(/\w+\[\w+]/g) ??
[];
for (const match of matches) {
const groups = match.match(/(?<type>\w+)\[(?<size>\w+)];?/)?.groups;
const { type, size } = groups;
const sizeLowerCase = this.checkSize(size);
const translatedType = this.translateType(type);
const replacer = `${translatedType}.${sizeLowerCase}`;
json = json.split(match).join(replacer);
}
return json;
}
static cKindFields(json) {
// `{u8 a,b;i32 x,y,z;}` => `{a:u8,b:u8,x:i32,y:i32,z:i32}`
// `{Xyz x,y,z;}` => `{x:Xyz,y:Xyz,z:Xyz}`
const matches = json.match(/\w+\s[\w,]+;/g) ?? // match `u8 a,b;`
[];
for (const match of matches) {
const groups = match.match(/(?<type>\w+)\s(?<fields>[\w,]+);/)?.groups;
const { type, fields } = groups;
const replacer = fields.split(',').map(field => `${field}:${type}`).join(',') + ',';
json = json.split(match).join(replacer);
}
return json;
}
static cKindStructs(json) {
/* C STRUCTS
1) 2)
typedef struct { struct Ab {
uint8_t x; int8 x;
uint8_t y; int8 y;
uint8_t z; };
} Xyz; */
// Warning: before this function all `u8 a,b;` become `a:u8,b:u8,`
// 1) `{typedef struct{uint8_t x;uint8_t y;uint8_t z;}Xyz;}` => `{Xyz:{x:uint8_t,y:uint8_t,z:uint8_t}}`
// 2) `{struct Ab{int8 x;int8 y;};}` => `{Ab:{x:int8,y:int8,z:int8}}`
const matches = json.match(/(typedef struct{[\w\s,:]+}\w+;|struct \w+{[\w\s,:]+};)/g) ??
[];
for (const match of matches) {
const groups = match.match(/typedef struct{(?<fields>[\w\s,:]+)}(?<type>\w+);/)?.groups ??
match.match(/struct (?<type>\w+){(?<fields>[\w\s,:]+)};/)?.groups;
const { fields, type } = groups;
const replacer = `${type}:{${fields}},`;
json = json.split(match).join(replacer);
}
return json;
}
static clearJson(json) {
json = json.replace(/,([}\]])/g, '$1'); // remove last useless ','
json = json.replace(/(.*),$/, '$1'); // remove last ','
json = json.replace(/([}\]])\s*([{[\w])/g, '$1,$2'); // add missing ',' between }] and {[
json = json.replace(/(\w+\.?\w*)/g, '"$1"'); // Add missing ""
return json;
}
static replaceModelTypesWithUserTypes(json, types) {
if (types) {
// Parse user types
const parsedTypesJson = this.parseTypes(types);
const parsedTypes = JSON.parse(parsedTypesJson);
// Prepare replacers
const typeEntries = Object
.entries(parsedTypes)
.map(([type, replacer]) => [`"${type}"`, JSON.stringify(replacer)]);
// Replace model with user types, stage 1
typeEntries.forEach(([type, replacer]) => json = json.split(type).join(replacer));
// Reverse user types to replace nested user types
typeEntries.reverse();
// Replace model with reverse user types, stage 2
typeEntries.forEach(([type, replacer]) => json = json.split(type).join(replacer));
}
return json;
}
static fixJson(json) {
if (!json) {
throw Error(`Invalid model '${json}'`);
}
// Reformat json
try {
const model = JSON.parse(json);
return JSON.stringify(model);
}
catch (error) {
throw Error(`Syntax error '${json}'. ${error.message}`);
}
}
static parseTypes(types) {
if (!types) {
return;
}
if (Array.isArray(types)) {
throw Error(`Invalid types '${types}'`);
}
switch (typeof types) {
case "string":
case "object":
return ModelParser.parseModel(types);
default:
throw Error(`Invalid types '${types}'`);
}
}
static parseModel(model, types) {
if (!model) {
throw Error(`Invalid model '${model ?? typeof model}'`);
}
let json = (typeof model) === 'string' ? model : JSON.stringify(model); // stringify
json = this.prepareJson(json);
json = this.arrayOfItems1(json);
json = this.arrayOfItems2(json);
json = this.staticOrDynamicArray(json);
json = this.justArray(json);
json = this.cKindFields(json);
json = this.cKindStructs(json);
json = this.clearJson(json);
json = this.replaceModelTypesWithUserTypes(json, types);
json = this.fixJson(json);
return json;
}
}
exports.ModelParser = ModelParser;
ModelParser._allowedLengthTypes = 'u8,u16,u32,u64,i8,i16,i32,i64'.split(',');
ModelParser._sTypes = 's,str,string'.split(',');
ModelParser._wsTypes = 'ws,wstr,wstring'.split(',');
ModelParser._bufTypes = 'buf,buffer'.split(',');
ModelParser._jTypes = 'j,json,any'.split(',');