diff --git a/.jshintrc b/.jshintrc index 24dd06b..c93947d 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,4 +1,4 @@ { - "undef": false, - "evil": true + "undef": false, + "evil": true } diff --git a/jpacks.js b/jpacks.js index 5f93edf..5dba847 100644 --- a/jpacks.js +++ b/jpacks.js @@ -5,9 +5,10 @@ * Binary data packing and unpacking. * @author * zswang (http://weibo.com/zswang) - * @version 0.1.0 - * @date 2015-11-01 + * @version 0.1.1 + * @date 2015-11-02 */ + function createSchema() { /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView */ @@ -208,6 +209,32 @@ return buffer; } Schema.pack = pack; + function stringify(obj) { + function scan(obj) { + if (typeof obj === 'object') { + if (obj instanceof Schema) { + return obj.schema; + } + var result = new obj.constructor(); + Object.keys(obj).forEach(function (key) { + result[key] = scan(obj[key]); + }); + return result; + } else if (typeof obj === 'function') { + return obj.schema; + } + return obj; + } + return JSON.stringify(scan(obj) || '').replace(/"/g, ''); + } + Schema.stringify = stringify; + Schema.prototype.toString = function () { + return stringify(this); + }; + return Schema; +} + function create() { + var Schema = createSchema(); /** * 基础类型 * @@ -298,7 +325,7 @@ })('set' + item.type), size: item.size, name: name, - namespace: 'base', + namespace: 'number', array: item.array }); Schema.register(name, schema); @@ -307,198 +334,155 @@ }); }); /** - * 声明数组类型 + * 声明指定长度或者下标的数组 * - * @param {string|Schema} itemSchema 数组元素类型 - * @param {number} count 元素个数 - * @return {Schema} 返回数据结构 + * @param {string|Schema} itemSchema 元素类型 + * @param {string|Schema|number=} count 下标类型或个数 + * @return {Schema|Function} 返回数据结构 * @example 调用示例 1 ```js var _ = jpacks; - var _schema = _.array(10, _.byte); - var ab = _.pack(_.array(10, _.byte), [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [1, 2, 3, 4, 0, 0, 0, 0, 0, 0] - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4, 0, 0, 0, 0, 0, 0] + var schema = jpacks.array('int16', 2); + console.log(String(schema)); + // > array(int16,2) + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [48, 49, 50, 51] + console.log(jpacks.unpack(schema, buffer)); + // > [12337, 12851] ``` - */ - function array(count, itemSchema) { - if (typeof count !== 'number') { - var temp = count; - count = itemSchema; - itemSchema = temp; - } - var schema = Schema.from(itemSchema); - if (!schema) { - throw new Error('Schema "' + itemSchema + '" not define.'); - } - var size = schema.size * count; - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (schema.array && options.littleEndian) { - /* TypeArray littleEndian is true */ - var offset = offsets[0]; - offsets[0] += size; - return [].slice.apply(new schema.array(buffer, offset, count)); - } - var result = []; - for (var i = 0; i < count; i++) { - result.push(Schema.unpack(schema, buffer, options, offsets)); - } - return result; - }, - pack: function _pack(value, options, buffer) { - if (schema.array && options.littleEndian) { - /* TypeArray littleEndian is true */ - var arrayBuffer = new ArrayBuffer(size); - var typeArray = new schema.array(arrayBuffer); - typeArray.set(value); - var uint8Array = new Uint8Array(arrayBuffer); - [].push.apply(buffer, uint8Array); - } - for (var i = 0; i < count; i++) { - Schema.pack(schema, value[i], options, buffer); - } - }, - name: schema.name + '[' + count + ']', - size: size, - namespace: 'array' - }); - } - Schema.register('array', array); - Schema.register('staticArray', array); - function bytes(size) { - return array(size, 'uint8'); - } - Schema.register('bytes', bytes); - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^(\w+)\[(\d+)\]$/); - if (match) { - var baseSchema = Schema.from(match[1]); - if (baseSchema) { - var arraySchema = array(parseInt(match[2]), baseSchema); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - } - }); - /** - * 声明指定长度的数组类型 - * - * @param {string|Schema} lengthSchema 长度类型 - * @param {string|Schema} itemSchema 元素类型 - * @return {Schema} 返回数据结构 - * @example 调用示例 1 + * @example 调用示例 2 ```js var _ = jpacks; - var _schema = _.dynamicArray(_.byte, _.byte); - var ab = _.pack(_schema, [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [4, 1, 2, 3, 4] - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4] + var schema = jpacks.array('int16', 'int8'); + console.log(String(schema)); + // > array(int16,int8) + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [ 2, 48, 49, 50, 51 ] + console.log(jpacks.unpack(schema, buffer)); + // > [ 12337, 12851 ] ``` - * @example 调用示例 2 + * @example 调用示例 3 ```js var _ = jpacks; - var _schema = _.dynamicArray(_.word, _.byte); - var ab = _.pack(_schema, [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 0, 0, 4, 1, 2, 3, 4] - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4] + var schema = jpacks.array('int16')(6); + console.log(String(schema)); + // > array(int16,6) + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [ 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0 ] + console.log(jpacks.unpack(schema, buffer)); + // > [ 12337, 12851, 0, 0, 0, 0 ] ``` - */ - function dynamicArray(lengthSchema, itemSchema) { - lengthSchema = Schema.from(lengthSchema); - if (lengthSchema.namespace !== 'base') { - throw new Error('Parameter "lengthSchema" is not a numeric type.'); - } - if (!itemSchema) { + */ + function array(itemSchema, count) { + if (typeof itemSchema === 'undefined') { throw new Error('Parameter "itemSchema" is undefined.'); } - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var length = Schema.unpack(lengthSchema, buffer, options, offsets); - return Schema.unpack(Schema.array(length, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - if (!value) { - Schema.pack(lengthSchema, 0, options, buffer); - } else { - Schema.pack(lengthSchema, value.length, options, buffer); - Schema.pack(Schema.array(value.length, itemSchema), value, options, buffer); + var creatorSchema = function (count) { + var size; + var countSchema; + if (typeof count === 'number') { + size = itemSchema.size * count; + } else { + countSchema = Schema.from(count); + if (countSchema.namespace !== 'number') { + throw new Error('Parameter "count" is not a numeric type.'); } - }, - name: 'dynamic array[..' + lengthSchema.name + '..]', - namespace: 'dynamicArray', - size: lengthSchema.size - }); - } - Schema.register('dynamicArray', dynamicArray); - function shortArray(schema) { - return dynamicArray('uint8', schema); + } + return new Schema({ + unpack: function _unpack(buffer, options, offsets) { + var length = count; + if (countSchema) { + length = Schema.unpack(countSchema, buffer, options, offsets); + } + if (itemSchema.array && options.littleEndian) { + size = countSchema.size * length; + /* TypeArray littleEndian is true */ + var offset = offsets[0]; + offsets[0] += size; + return [].slice.apply(new itemSchema.array(buffer, offset, length)); + } + var result = []; + for (var i = 0; i < length; i++) { + result.push(Schema.unpack(itemSchema, buffer, options, offsets)); + } + return result; + }, + pack: function _pack(value, options, buffer) { + var length = count; + if (countSchema) { + length = value ? value.length : 0; + Schema.pack(countSchema, length, options, buffer); + } + if (itemSchema.array && options.littleEndian) { + size = itemSchema.size * length; + /* TypeArray littleEndian is true */ + var arrayBuffer = new ArrayBuffer(size); + var typeArray = new itemSchema.array(arrayBuffer); + typeArray.set(value); + var uint8Array = new Uint8Array(arrayBuffer); + [].push.apply(buffer, uint8Array); + } + for (var i = 0; i < length; i++) { + Schema.pack(itemSchema, value[i], options, buffer); + } + }, + schema: 'array(' + [Schema.stringify(itemSchema), Schema.stringify(count)] + ')', + namespace: 'array', + size: size + }); + }; + creatorSchema.name = 'array(' + itemSchema.name + ')'; + if (arguments.length === 1) { + return creatorSchema; + } else { + return creatorSchema(count); + } + } + Schema.register('array', array); + function shortArray(itemSchema) { + return array(itemSchema, 'uint8'); } Schema.register('shortArray', shortArray); - function smallArray(schema) { - return dynamicArray('uint16', schema); + function smallArray(itemSchema) { + return array(itemSchema, 'uint16'); } Schema.register('smallArray', smallArray); - function longArray(schema) { - return dynamicArray('uint32', schema); + function longArray(itemSchema) { + return array(itemSchema, 'uint32'); } Schema.register('longArray', longArray); - // 'int8[.]' - shortArray - // 'int8[..]' - smallArray - // 'int8[....]' - longArray - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^(\w+)\[(\.|\..|\....)\]$/); - if (match) { - var baseSchema = Schema.from(match[1]); - if (baseSchema) { - var lengthSchema = 'uint16'; - switch (match[2]) { - case '.': - lengthSchema = 'uint8' - break; - case '....': - lengthSchema = 'uint32' - break; - } - var arraySchema = dynamicArray(lengthSchema, baseSchema); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - } - }); + function bytes(count) { + return Schema.array('uint8', count); + } + Schema.register('bytes', bytes); /** * 定义一个对象结构 * * @param {object} schema 数据结构 * @return {Schema} 返回构建的数据结构 */ - function object(schema) { - if (typeof schema !== 'object') { + function object(objectSchema) { + if (typeof objectSchema !== 'object') { throw new Error('Parameter "schemas" must be a object type.'); } - if (schema instanceof Schema) { - return schema; + if (objectSchema instanceof Schema) { + return objectSchema; } + var names = Schema.stringify(objectSchema); + var keys = Object.keys(objectSchema); return new Schema({ unpack: function _unpack(buffer, options, offsets) { - var result = {}; + var result = new objectSchema.constructor(); var $scope = options.$scope; options.$scope = result; - Object.keys(schema).forEach(function (key) { - result[key] = Schema.unpack(schema[key], buffer, options, offsets); + keys.forEach(function (key) { + result[key] = Schema.unpack(objectSchema[key], buffer, options, offsets); }); options.$scope = $scope; return result; @@ -506,13 +490,14 @@ pack: function _pack(value, options, buffer) { var $scope = options.$scope; options.$scope = value; - Object.keys(schema).forEach(function (key) { - Schema.pack(schema[key], value[key], options, buffer); + keys.forEach(function (key) { + Schema.pack(objectSchema[key], value[key], options, buffer); }); options.$scope = $scope; }, - object: schema, - name: 'object {}' + object: objectSchema, + schema: 'object(' + names + ')', + namespace: 'object' }); }; Schema.register('object', object); @@ -589,62 +574,43 @@ }, size: size, object: schemas, - name: 'union {}' + schema: 'union(' + Schema.stringify(size) + ')' }); } Schema.register('union', union); /** - * 创建条件类型 + * 定义一个枚举结构 * - * @param {Object} schemas 条件类型结构,第一个字段为条件字段,其他字段为数组。数组第一元素表示命中条件,第二位类型 - * @return {Schema} 返回联合类型 - * @example 调用示例 1 - ```js - var _ = jpacks; - var _schema = _.cases({ - type: _.shortString, - name: ['name', _.shortString], - age: ['age', _.byte] - }); - var ab = _.pack(_schema, { - type: 'name', - name: 'tom' - }); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [4, 110, 97, 109, 101, 3, 116, 111, 109] - console.log(_.unpack(_schema, u8a)); - // -> Object {type: "name", name: "tom"} - var ab2 = _.pack(_schema, { - type: 'age', - age: 23 - }); - var u8a2 = new Uint8Array(ab2); - console.log(u8a2); - // -> [3, 97, 103, 101, 23] - console.log(_.unpack(_schema, u8a2)); - // -> Object {type: "age", age: 23} - ``` - */ - function cases(schemas) { - if (typeof schemas !== 'object') { - throw new Error('Parameter "schemas" must be a object type.'); + * @param {Schema} baseSchema 枚举结构的基础类型 + * @param {Array|Object} map 枚举类型字典 + * @return {Schema} 返回构建的数据结构 + */ + function enums(baseSchema, map) { + baseSchema = Schema.from(baseSchema); + if (!baseSchema) { + throw new Error('Parameter "baseSchema" is undefined.'); } - if (schemas instanceof Schema) { - throw new Error('Parameter "schemas" cannot be a Schema object.'); + if (baseSchema.namespace !== 'base') { + throw new Error('Parameter "baseSchema" is not a numeric type.'); } - var keys = Object.keys(schemas); - var patternName = keys[0]; - var patternSchema = schemas[patternName]; - keys = keys.slice(1); + if (typeof map !== 'object') { + throw new Error('Parameter "map" must be a object type.'); + } + if (map instanceof Array) { + var temp = {}; + map.forEach(function (item, index) { + temp[item] = index; + }); + map = temp; + } + var keys = Object.keys(map); return new Schema({ unpack: function _unpack(buffer, options, offsets) { - var result = {}; - var patternValue = Schema.unpack(patternSchema, buffer, options, offsets); - result[patternName] = patternValue; + var baseValue = Schema.unpack(baseSchema, buffer, options, offsets); + var result; keys.every(function (key) { - if (patternValue === schemas[key][0]) { - result[key] = Schema.unpack(schemas[key][1], buffer, options, offsets); + if (map[key] === baseValue) { + result = key; return false; } return true; @@ -652,21 +618,22 @@ return result; }, pack: function _pack(value, options, buffer) { - var patternValue = value[patternName]; - Schema.pack(patternSchema, patternValue, options, buffer); - keys.every(function (key) { - if (patternValue === schemas[key][0]) { - Schema.pack(schemas[key][1], value[key], options, buffer); + if (keys.every(function (key) { + if (key === value) { + Schema.pack(baseSchema, map[key], options, buffer); return false; } return true; - }); + })) { + throw new Error('Not find enum "' + value + '".'); + }; }, - name: 'cases{' + patternName + '}', - namespace: 'cases' + map: map, + schema: 'enum(' + [Schema.stringify(baseSchema), Schema.stringify(map)] + ')', + namespace: 'base' }); - } - Schema.register('cases', cases); + }; + Schema.register('enums', enums); /** * 对字符串进行 utf8 编码 * @@ -723,11 +690,10 @@ }); } } - Schema.stringBytes = stringBytes; /** * 声明指定长度的字符串 * - * @param {number} size 字节个数 + * @param {number|string|Schema} size 字节个数下标类型 * @return {Schema} 返回数据结构 */ function string(size) { @@ -743,67 +709,12 @@ pack: function _pack(value, options, buffer) { Schema.pack(schema, stringBytes(value), options, buffer); }, - namespace: 'staticString', - name: 'string{' + size + '}', + namespace: 'string', + name: 'string(' + Schema.stringify(size) + ')', size: size }); } Schema.register('string', string); - Schema.register('staticString', string); - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^string\{(\d+)\}$/); - if (match) { - var arraySchema = string(parseInt(match[1])); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - }); - /** - * 声明指定长度的字符串 - * - * @param {string|Schema} lengthSchema 长度类型 - * @return {Schema} 返回数据结构 - * @example 调用示例 - ```js - var _ = jpacks; - var _schema = _.dynamicString('int32'); - var ab = _.pack(_schema, '你好 World!'); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 0, 0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] - console.log(_.unpack(_schema, u8a)); - // -> 你好 World! - ``` - */ - function dynamicString(lengthSchema) { - lengthSchema = Schema.from(lengthSchema); - if (!lengthSchema) { - throw new Error('Parameter "lengthSchema" is undefined.'); - } - if (lengthSchema.namespace !== 'base') { - throw new Error('Parameter "lengthSchema" is not a numeric type.'); - } - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var length = Schema.unpack(lengthSchema, buffer, options, offsets); - return Schema.unpack(Schema.string(length), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - if (!value) { - Schema.pack(lengthSchema, 0, options, buffer); - } else { - var bytes = Schema.stringBytes(value); - Schema.pack(lengthSchema, bytes.length, options, buffer); - Schema.pack(Schema.bytes(bytes.length), bytes, options, buffer); - } - }, - namespace: 'dynamicString', - name: 'string{..' + lengthSchema.name + '..}' - }); - } - Schema.register('dynamicString', dynamicString); /** * 短字符串类型 * @@ -820,7 +731,7 @@ // -> 你好 World! ``` */ - Schema.register('shortString', dynamicString('uint8')); + Schema.register('shortString', string('uint8')); /** * 长字符串类型 * @@ -837,9 +748,9 @@ // -> 你好 World! ``` */ - Schema.register('smallString', dynamicString('uint16')); + Schema.register('smallString', string('uint16')); /** - * 长字符串类型 + * 超长字符串类型 * * @return {Schema} 返回数据结构 * @example 调用示例 @@ -854,116 +765,7 @@ // -> 你好 World! ``` */ - Schema.register('longString', dynamicString('uint32')); - function dependArray(field, itemSchema) { - if (typeof field !== 'string') { - throw new Error('Parameter "field" must be a string.'); - } - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (!options.$scope) { - throw new Error('Unpack must running in object.'); - } - var length = options.$scope[field]; - if (typeof length !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - return Schema.unpack(Schema.staticArray(length, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - var length = options.$scope[field]; - if (typeof length !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - Schema.pack(Schema.array(length, itemSchema), value, options, buffer); - }, - name: 'depend array{' + field + '}', - namespace: 'dependArray' - }); - } - Schema.register('dependArray', dependArray); - function dependString(field, itemSchema) { - if (typeof field !== 'string') { - throw new Error('Parameter "field" must be a string.'); - } - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (!options.$scope) { - throw new Error('Unpack must running in object.'); - } - var size = options.$scope[field]; - if (typeof size !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - return Schema.unpack(Schema.staticString(size, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - var size = options.$scope[field]; - if (typeof size !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - Schema.pack(Schema.string(size), value, options, buffer); - }, - name: 'depend string{' + field + '}', - namespace: 'dependString' - }); - } - Schema.register('dependString', dependString); - /** - * 定义一个枚举结构 - * - * @param {Schema} baseSchema 枚举结构的基础类型 - * @param {Array|Object} map 枚举类型字典 - * @return {Schema} 返回构建的数据结构 - */ - function enums(baseSchema, map) { - baseSchema = Schema.from(baseSchema); - if (!baseSchema) { - throw new Error('Parameter "baseSchema" is undefined.'); - } - if (baseSchema.namespace !== 'base') { - throw new Error('Parameter "baseSchema" is not a numeric type.'); - } - if (typeof map !== 'object') { - throw new Error('Parameter "map" must be a object type.'); - } - if (map instanceof Array) { - var temp = {}; - map.forEach(function (item, index) { - temp[item] = index; - }); - map = temp; - } - var keys = Object.keys(map); - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var baseValue = Schema.unpack(baseSchema, buffer, options, offsets); - var result; - keys.every(function (key) { - if (map[key] === baseValue) { - result = key; - return false; - } - return true; - }); - return result; - }, - pack: function _pack(value, options, buffer) { - if (keys.every(function (key) { - if (key === value) { - Schema.pack(baseSchema, map[key], options, buffer); - return false; - } - return true; - })) { - throw new Error('Not find enum "' + value + '".'); - }; - }, - map: map, - name: 'enum {}' - }); - }; - Schema.register('enums', enums); + Schema.register('longString', string('uint32')); /** * 以零号字符结尾的字符串 * @@ -997,11 +799,116 @@ Schema.pack('byte', 0, options, buffer); }, namespace: 'cstring', - name: 'cstring' + schema: 'cstring' }); Schema.register('cstring', cstring); Schema.register('pchar', cstring); - var exports = Schema; + /** + * 创建条件类型 + * + * @param {Array of array} patterns 数组第一元素表示命中条件,第二位类型 + * @return {Schema} 返回条件类型 + * @example 调用示例 1 + ```js + var _ = jpacks; + var _schema = { + name: ['name', _.shortString], + age: ['age', _.byte] + }; + var ab = _.pack(_schema, { + type: 'name', + name: 'tom' + }); + var u8a = new Uint8Array(ab); + console.log(u8a); + // -> [4, 110, 97, 109, 101, 3, 116, 111, 109] + console.log(_.unpack(_schema, u8a)); + // -> Object {type: "name", name: "tom"} + var ab2 = _.pack(_schema, { + type: 'age', + age: 23 + }); + var u8a2 = new Uint8Array(ab2); + console.log(u8a2); + // -> [3, 97, 103, 101, 23] + console.log(_.unpack(_schema, u8a2)); + // -> Object {type: "age", age: 23} + ``` + */ + function cases(patterns) { + /**/ + if (typeof patterns !== 'object') { + throw new Error('Parameter "patterns" must be a object type.'); + } + if (patterns instanceof Schema) { + throw new Error('Parameter "patterns" cannot be a Schema object.'); + } + if (!(patterns instanceof Array)) { + throw new Error('Parameter "patterns" must be a array.'); + } + /**/ + var schemaCreator = function (value) { + for (var i = 0; i < patterns.length; i++) { + if (patterns[i][0] === value) { + return patterns[i][1]; + } + } + }; + schemaCreator.schema = 'cases(' + Schema.stringify(patterns) + ')'; + schemaCreator.namespace = 'cases'; + return schemaCreator; + } + Schema.register('cases', cases); + /** + * 声明字段依赖结构 + * + * @param {string} field 字段名 + * @param {Function} schemaCreator 创建数据结构的方法 + * [[[ + * @param {Any} value 传递值 + * @return {Schema} 返回数据结构 + * function schemaCreator(value) {} + * ]]] + */ + function depend(field, schemaCreator) { + if (typeof field !== 'string') { + throw new Error('Parameter "field" must be a string.'); + } + if (typeof schemaCreator !== 'function') { + throw new Error('Parameter "field" must be a function.'); + } + return new Schema({ + unpack: function _unpack(buffer, options, offsets) { + if (!options.$scope) { + throw new Error('Unpack must running in object.'); + } + var fieldValue = options.$scope[field]; + if (typeof fieldValue === 'undefined') { + throw new Error('Field "' + field + '" is undefined.'); + } + return Schema.unpack(schemaCreator(fieldValue), buffer, options, offsets); + }, + pack: function _pack(value, options, buffer) { + var fieldValue = options.$scope[field]; + if (typeof fieldValue === 'undefined') { + throw new Error('Field "' + field + '" is undefined.'); + } + Schema.pack(schemaCreator(fieldValue), value, options, buffer); + }, + schema: 'depend(' + [field, Schema.stringify(schemaCreator)] + ')', + namespace: 'depend' + }); + } + Schema.register('depend', depend); + function dependArray(field, itemSchema) { + return depend(field, Schema.array(itemSchema)); + } + Schema.register('dependArray', dependArray); + return Schema; + } + var root = create(); + root.create = create; + var exports = root; if (typeof define === 'function') { if (define.amd || define.cmd) { define(function () { diff --git a/jpacks.min.js b/jpacks.min.js index d99001b..6b4a4f4 100644 --- a/jpacks.min.js +++ b/jpacks.min.js @@ -1 +1 @@ -!function(r){function e(r){var e=this;Object.keys(r).forEach(function(n){e[n]=r[n]})}function n(r){j=r}function t(r){if(r instanceof ArrayBuffer)return r;var e=new ArrayBuffer(r.length),n=new Uint8Array(e,0,r.length);return n.set(r),e}function a(r,n,a,i){var o=e.from(r);if(!o)throw new Error('Parameter schema "'+r+'" is unregister.');return n=t(n),a=a||{},i=i||[0],"undefined"==typeof a.littleEndian&&(a.littleEndian=j),o.unpack(n,a,i)}function i(r,n,t,a){var i=e.from(r);if(!i)throw new Error('Parameter schema "'+r+'" is unregister.');return a=a||[],t=t||{},"undefined"==typeof t.littleEndian&&(t.littleEndian=j),i.pack(n,t,a),a}function o(r,n){if("number"!=typeof r){var t=r;r=n,n=t}var a=e.from(n);if(!a)throw new Error('Schema "'+n+'" not define.');var i=a.size*r;return new e({unpack:function(n,t,o){if(a.array&&t.littleEndian){var c=o[0];return o[0]+=i,[].slice.apply(new a.array(n,c,r))}for(var u=[],f=0;r>f;f++)u.push(e.unpack(a,n,t,o));return u},pack:function(n,t,o){if(a.array&&t.littleEndian){var c=new ArrayBuffer(i),u=new a.array(c);u.set(n);var f=new Uint8Array(c);[].push.apply(o,f)}for(var s=0;r>s;s++)e.pack(a,n[s],t,o)},name:a.name+"["+r+"]",size:i,namespace:"array"})}function c(r){return o(r,"uint8")}function u(r,n){if(r=e.from(r),"base"!==r.namespace)throw new Error('Parameter "lengthSchema" is not a numeric type.');if(!n)throw new Error('Parameter "itemSchema" is undefined.');return new e({unpack:function(t,a,i){var o=e.unpack(r,t,a,i);return e.unpack(e.array(o,n),t,a,i)},pack:function(t,a,i){t?(e.pack(r,t.length,a,i),e.pack(e.array(t.length,n),t,a,i)):e.pack(r,0,a,i)},name:"dynamic array[.."+r.name+"..]",namespace:"dynamicArray",size:r.size})}function f(r){return u("uint8",r)}function s(r){return u("uint16",r)}function p(r){return u("uint32",r)}function m(r){if("object"!=typeof r)throw new Error('Parameter "schemas" must be a object type.');return r instanceof e?r:new e({unpack:function(n,t,a){var i={},o=t.$scope;return t.$scope=i,Object.keys(r).forEach(function(o){i[o]=e.unpack(r[o],n,t,a)}),t.$scope=o,i},pack:function(n,t,a){var i=t.$scope;t.$scope=n,Object.keys(r).forEach(function(i){e.pack(r[i],n[i],t,a)}),t.$scope=i},object:r,name:"object {}"})}function y(r,n){if("number"!=typeof r){var t=r;r=n,n=t}if("number"!=typeof r)throw new Error('Parameter "size" must be a numeric type.');if("object"!=typeof n)throw new Error('Parameter "schemas" must be a object type.');if(n instanceof e)throw new Error('Parameter "schemas" cannot be a Schema object.');var a=Object.keys(n);return new e({unpack:function(t,i,o){var c=o[0],u={};return a.forEach(function(r){o[0]=c,u[r]=e.unpack(n[r],t,i,o)}),o[0]+=r,u},pack:function(t,i,o){var c=new ArrayBuffer(r),u=new Uint8Array(c);a.forEach(function(r){var a=[];e.pack(n[r],t[r],i,a),u.set(a)}),[].push.apply(o,u)},size:r,object:n,name:"union {}"})}function g(r){if("object"!=typeof r)throw new Error('Parameter "schemas" must be a object type.');if(r instanceof e)throw new Error('Parameter "schemas" cannot be a Schema object.');var n=Object.keys(r),t=n[0],a=r[t];return n=n.slice(1),new e({unpack:function(i,o,c){var u={},f=e.unpack(a,i,o,c);return u[t]=f,n.every(function(n){return f===r[n][0]?(u[n]=e.unpack(r[n][1],i,o,c),!1):!0}),u},pack:function(i,o,c){var u=i[t];e.pack(a,u,o,c),n.every(function(n){return u===r[n][0]?(e.pack(r[n][1],i[n],o,c),!1):!0})},name:"cases{"+t+"}",namespace:"cases"})}function h(r){return String(r).replace(/[\u0080-\u07ff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(192|e>>6,128|63&e)}).replace(/[\u0800-\uffff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e)})}function w(r){return String(r).replace(/[\u00c0-\u00df][\u0080-\u00bf]/g,function(r){var e=(31&r.charCodeAt(0))<<6|63&r.charCodeAt(1);return String.fromCharCode(e)}).replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,function(r){var e=(15&r.charCodeAt(0))<<12|(63&r.charCodeAt(1))<<6|63&r.charCodeAt(2);return String.fromCharCode(e)})}function d(r,e){return"undefined"!=typeof Buffer?new Buffer(r,e):h(r).split("").map(function(r){return r.charCodeAt()})}function l(r){var n=e.array(r,"uint8");return new e({unpack:function(r,t,a){var i=e.unpack(n,r,t,a);return"undefined"!=typeof Buffer?new Buffer(i).toString():w(String.fromCharCode.apply(String,i))},pack:function(r,t,a){e.pack(n,d(r),t,a)},namespace:"staticString",name:"string{"+r+"}",size:r})}function k(r){if(r=e.from(r),!r)throw new Error('Parameter "lengthSchema" is undefined.');if("base"!==r.namespace)throw new Error('Parameter "lengthSchema" is not a numeric type.');return new e({unpack:function(n,t,a){var i=e.unpack(r,n,t,a);return e.unpack(e.string(i),n,t,a)},pack:function(n,t,a){if(n){var i=e.stringBytes(n);e.pack(r,i.length,t,a),e.pack(e.bytes(i.length),i,t,a)}else e.pack(r,0,t,a)},namespace:"dynamicString",name:"string{.."+r.name+"..}"})}function v(r,n){if("string"!=typeof r)throw new Error('Parameter "field" must be a string.');return new e({unpack:function(t,a,i){if(!a.$scope)throw new Error("Unpack must running in object.");var o=a.$scope[r];if("number"!=typeof o)throw new Error('Field "'+r+'" must be a number.');return e.unpack(e.staticArray(o,n),t,a,i)},pack:function(t,a,i){var o=a.$scope[r];if("number"!=typeof o)throw new Error('Field "'+r+'" must be a number.');e.pack(e.array(o,n),t,a,i)},name:"depend array{"+r+"}",namespace:"dependArray"})}function b(r,n){if("string"!=typeof r)throw new Error('Parameter "field" must be a string.');return new e({unpack:function(t,a,i){if(!a.$scope)throw new Error("Unpack must running in object.");var o=a.$scope[r];if("number"!=typeof o)throw new Error('Field "'+r+'" must be a number.');return e.unpack(e.staticString(o,n),t,a,i)},pack:function(n,t,a){var i=t.$scope[r];if("number"!=typeof i)throw new Error('Field "'+r+'" must be a number.');e.pack(e.string(i),n,t,a)},name:"depend string{"+r+"}",namespace:"dependString"})}function E(r,n){if(r=e.from(r),!r)throw new Error('Parameter "baseSchema" is undefined.');if("base"!==r.namespace)throw new Error('Parameter "baseSchema" is not a numeric type.');if("object"!=typeof n)throw new Error('Parameter "map" must be a object type.');if(n instanceof Array){var t={};n.forEach(function(r,e){t[r]=e}),n=t}var a=Object.keys(n);return new e({unpack:function(t,i,o){var c,u=e.unpack(r,t,i,o);return a.every(function(r){return n[r]===u?(c=r,!1):!0}),c},pack:function(t,i,o){if(a.every(function(a){return a===t?(e.pack(r,n[a],i,o),!1):!0}))throw new Error('Not find enum "'+t+'".')},map:n,name:"enum {}"})}var A={};e.register=function(r,n){if("undefined"==typeof r)throw new Error('Parameter "name" is undefined.');A[r]=n,e[r]||(e[r]=n)};var S=[function(r){for(var e={},n=r;"string"==typeof n;){if(e[n])return;n=A[n],e[n]=!0}return n}];e.pushPattern=function(r){"function"==typeof r&&S.push(r)},e.from=function(r){if("undefined"!=typeof r){if(r instanceof e)return r;for(var n=-1,t=0;ts;s++)c.push(g.unpack(r,a,i,o));return c},pack:function(a,i,o){var f=e;if(t&&(f=a?a.length:0,g.pack(t,f,i,o)),r.array&&i.littleEndian){n=r.size*f;var u=new ArrayBuffer(n),c=new r.array(u);c.set(a);var s=new Uint8Array(u);[].push.apply(o,s)}for(var p=0;f>p;p++)g.pack(r,a[p],i,o)},schema:"array("+[g.stringify(r),g.stringify(e)]+")",namespace:"array",size:n})};return n.name="array("+r.name+")",1===arguments.length?n:n(e)}function n(e){return r(e,"uint8")}function t(e){return r(e,"uint16")}function a(e){return r(e,"uint32")}function i(r){return g.array("uint8",r)}function o(r){if("object"!=typeof r)throw new Error('Parameter "schemas" must be a object type.');if(r instanceof g)return r;var e=g.stringify(r),n=Object.keys(r);return new g({unpack:function(e,t,a){var i=new r.constructor,o=t.$scope;return t.$scope=i,n.forEach(function(n){i[n]=g.unpack(r[n],e,t,a)}),t.$scope=o,i},pack:function(e,t,a){var i=t.$scope;t.$scope=e,n.forEach(function(n){g.pack(r[n],e[n],t,a)}),t.$scope=i},object:r,schema:"object("+e+")",namespace:"object"})}function f(r,e){if("number"!=typeof r){var n=r;r=e,e=n}if("number"!=typeof r)throw new Error('Parameter "size" must be a numeric type.');if("object"!=typeof e)throw new Error('Parameter "schemas" must be a object type.');if(e instanceof g)throw new Error('Parameter "schemas" cannot be a Schema object.');var t=Object.keys(e);return new g({unpack:function(n,a,i){var o=i[0],f={};return t.forEach(function(r){i[0]=o,f[r]=g.unpack(e[r],n,a,i)}),i[0]+=r,f},pack:function(n,a,i){var o=new ArrayBuffer(r),f=new Uint8Array(o);t.forEach(function(r){var t=[];g.pack(e[r],n[r],a,t),f.set(t)}),[].push.apply(i,f)},size:r,object:e,schema:"union("+g.stringify(r)+")"})}function u(r,e){if(r=g.from(r),!r)throw new Error('Parameter "baseSchema" is undefined.');if("base"!==r.namespace)throw new Error('Parameter "baseSchema" is not a numeric type.');if("object"!=typeof e)throw new Error('Parameter "map" must be a object type.');if(e instanceof Array){var n={};e.forEach(function(r,e){n[r]=e}),e=n}var t=Object.keys(e);return new g({unpack:function(n,a,i){var o,f=g.unpack(r,n,a,i);return t.every(function(r){return e[r]===f?(o=r,!1):!0}),o},pack:function(n,a,i){if(t.every(function(t){return t===n?(g.pack(r,e[t],a,i),!1):!0}))throw new Error('Not find enum "'+n+'".')},map:e,schema:"enum("+[g.stringify(r),g.stringify(e)]+")",namespace:"base"})}function c(r){return String(r).replace(/[\u0080-\u07ff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(192|e>>6,128|63&e)}).replace(/[\u0800-\uffff]/g,function(r){var e=r.charCodeAt(0);return String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e)})}function s(r){return String(r).replace(/[\u00c0-\u00df][\u0080-\u00bf]/g,function(r){var e=(31&r.charCodeAt(0))<<6|63&r.charCodeAt(1);return String.fromCharCode(e)}).replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,function(r){var e=(15&r.charCodeAt(0))<<12|(63&r.charCodeAt(1))<<6|63&r.charCodeAt(2);return String.fromCharCode(e)})}function p(r,e){return"undefined"!=typeof Buffer?new Buffer(r,e):c(r).split("").map(function(r){return r.charCodeAt()})}function y(r){var e=g.array(r,"uint8");return new g({unpack:function(r,n,t){var a=g.unpack(e,r,n,t);return"undefined"!=typeof Buffer?new Buffer(a).toString():s(String.fromCharCode.apply(String,a))},pack:function(r,n,t){g.pack(e,p(r),n,t)},namespace:"string",name:"string("+g.stringify(r)+")",size:r})}function m(r){if("object"!=typeof r)throw new Error('Parameter "patterns" must be a object type.');if(r instanceof g)throw new Error('Parameter "patterns" cannot be a Schema object.');if(!(r instanceof Array))throw new Error('Parameter "patterns" must be a array.');var e=function(e){for(var n=0;n*/ /**/ - var Schema = require('./schema'); + var createSchema = require('./schema'); /**/ + + function create() { + var Schema = createSchema(); + /**/ + require('./schemas/number')(Schema); - /**/ - /**/ - require('./schemas/base')(Schema); - require('./schemas/staticArray')(Schema); - require('./schemas/dynamicArray')(Schema); + require('./schemas/array')(Schema); + require('./schemas/bytes')(Schema); - require('./schemas/object')(Schema); - require('./schemas/union')(Schema); - require('./schemas/cases')(Schema); + require('./schemas/object')(Schema); + require('./schemas/union')(Schema); + require('./schemas/enums')(Schema); - require('./schemas/staticString')(Schema); - require('./schemas/dynamicString')(Schema); + require('./schemas/string')(Schema); - require('./schemas/dependArray')(Schema); - require('./schemas/dependString')(Schema); + require('./schemas/cstring')(Schema); - require('./schemas/enums')(Schema); - require('./schemas/cstring')(Schema); + require('./schemas/cases')(Schema); + require('./schemas/depend')(Schema); /**/ - /**/ - - var exports = Schema; + + return Schema; + } + var root = create(); + root.create = create; + var exports = root; if (typeof define === 'function') { if (define.amd || define.cmd) { diff --git a/src/schema.js b/src/schema.js index 57969ed..87ac16a 100644 --- a/src/schema.js +++ b/src/schema.js @@ -1,5 +1,5 @@ +/**/ function createSchema() { - /**/ /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView */ @@ -217,9 +217,31 @@ function createSchema() { return buffer; } Schema.pack = pack; - /**/ + function stringify(obj) { + function scan(obj) { + if (typeof obj === 'object') { + if (obj instanceof Schema) { + return obj.schema; + } + var result = new obj.constructor(); + Object.keys(obj).forEach(function (key) { + result[key] = scan(obj[key]); + }); + return result; + } else if (typeof obj === 'function') { + return obj.schema; + } + return obj; + } + return JSON.stringify(scan(obj) || '').replace(/"/g, ''); + } + Schema.stringify = stringify; + + Schema.prototype.toString = function () { + return stringify(this); + }; return Schema; } - -module.exports = createSchema(); \ No newline at end of file +/**/ +module.exports = createSchema; \ No newline at end of file diff --git a/src/schemas/array.js b/src/schemas/array.js new file mode 100644 index 0000000..e6506e9 --- /dev/null +++ b/src/schemas/array.js @@ -0,0 +1,137 @@ +module.exports = function (Schema) { + /**/ + /** + * 声明指定长度或者下标的数组 + * + * @param {string|Schema} itemSchema 元素类型 + * @param {string|Schema|number=} count 下标类型或个数 + * @return {Schema|Function} 返回数据结构 + * @example 调用示例 1 + ```js + var _ = jpacks; + var schema = jpacks.array('int16', 2); + console.log(String(schema)); + // > array(int16,2) + + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [48, 49, 50, 51] + + console.log(jpacks.unpack(schema, buffer)); + // > [12337, 12851] + ``` + * @example 调用示例 2 + ```js + var _ = jpacks; + var schema = jpacks.array('int16', 'int8'); + console.log(String(schema)); + // > array(int16,int8) + + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [ 2, 48, 49, 50, 51 ] + + console.log(jpacks.unpack(schema, buffer)); + // > [ 12337, 12851 ] + ``` + * @example 调用示例 3 + ```js + var _ = jpacks; + var schema = jpacks.array('int16')(6); + console.log(String(schema)); + // > array(int16,6) + + var value = [12337, 12851]; + var buffer = jpacks.pack(schema, value); + console.log(buffer); + // > [ 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0 ] + + console.log(jpacks.unpack(schema, buffer)); + // > [ 12337, 12851, 0, 0, 0, 0 ] + ``` + */ + function array(itemSchema, count) { + if (typeof itemSchema === 'undefined') { + throw new Error('Parameter "itemSchema" is undefined.'); + } + + var creatorSchema = function (count) { + var size; + var countSchema; + if (typeof count === 'number') { + size = itemSchema.size * count; + } else { + countSchema = Schema.from(count); + if (countSchema.namespace !== 'number') { + throw new Error('Parameter "count" is not a numeric type.'); + } + } + + return new Schema({ + unpack: function _unpack(buffer, options, offsets) { + var length = count; + if (countSchema) { + length = Schema.unpack(countSchema, buffer, options, offsets); + } + if (itemSchema.array && options.littleEndian) { + size = countSchema.size * length; + /* TypeArray littleEndian is true */ + var offset = offsets[0]; + offsets[0] += size; + return [].slice.apply(new itemSchema.array(buffer, offset, length)); + } + var result = []; + for (var i = 0; i < length; i++) { + result.push(Schema.unpack(itemSchema, buffer, options, offsets)); + } + return result; + }, + pack: function _pack(value, options, buffer) { + var length = count; + if (countSchema) { + length = value ? value.length : 0; + Schema.pack(countSchema, length, options, buffer); + } + if (itemSchema.array && options.littleEndian) { + size = itemSchema.size * length; + /* TypeArray littleEndian is true */ + var arrayBuffer = new ArrayBuffer(size); + var typeArray = new itemSchema.array(arrayBuffer); + typeArray.set(value); + var uint8Array = new Uint8Array(arrayBuffer); + [].push.apply(buffer, uint8Array); + } + + for (var i = 0; i < length; i++) { + Schema.pack(itemSchema, value[i], options, buffer); + } + }, + schema: 'array(' + [Schema.stringify(itemSchema), Schema.stringify(count)] + ')', + namespace: 'array', + size: size + }); + }; + creatorSchema.name = 'array(' + itemSchema.name + ')'; + if (arguments.length === 1) { + return creatorSchema; + } else { + return creatorSchema(count); + } + } + Schema.register('array', array); + function shortArray(itemSchema) { + return array(itemSchema, 'uint8'); + } + Schema.register('shortArray', shortArray); + function smallArray(itemSchema) { + return array(itemSchema, 'uint16'); + } + Schema.register('smallArray', smallArray); + function longArray(itemSchema) { + return array(itemSchema, 'uint32'); + } + Schema.register('longArray', longArray); + /**/ +}; \ No newline at end of file diff --git a/src/schemas/bytes.js b/src/schemas/bytes.js new file mode 100644 index 0000000..5bf47c2 --- /dev/null +++ b/src/schemas/bytes.js @@ -0,0 +1,8 @@ +module.exports = function (Schema) { + /**/ + function bytes(count) { + return Schema.array('uint8', count); + } + Schema.register('bytes', bytes); + /**/ +}; \ No newline at end of file diff --git a/src/schemas/cases.js b/src/schemas/cases.js index fee51b1..ac4a88d 100644 --- a/src/schemas/cases.js +++ b/src/schemas/cases.js @@ -1,18 +1,17 @@ -module.exports = function defineCases(Schema) { +module.exports = function (Schema) { /**/ /** * 创建条件类型 * - * @param {Object} schemas 条件类型结构,第一个字段为条件字段,其他字段为数组。数组第一元素表示命中条件,第二位类型 - * @return {Schema} 返回联合类型 + * @param {Array of array} patterns 数组第一元素表示命中条件,第二位类型 + * @return {Schema} 返回条件类型 * @example 调用示例 1 ```js var _ = jpacks; - var _schema = _.cases({ - type: _.shortString, + var _schema = { name: ['name', _.shortString], age: ['age', _.byte] - }); + }; var ab = _.pack(_schema, { type: 'name', name: 'tom' @@ -36,46 +35,29 @@ module.exports = function defineCases(Schema) { // -> Object {type: "age", age: 23} ``` */ - function cases(schemas) { - if (typeof schemas !== 'object') { - throw new Error('Parameter "schemas" must be a object type.'); + function cases(patterns) { + /**/ + if (typeof patterns !== 'object') { + throw new Error('Parameter "patterns" must be a object type.'); } - if (schemas instanceof Schema) { - throw new Error('Parameter "schemas" cannot be a Schema object.'); + if (patterns instanceof Schema) { + throw new Error('Parameter "patterns" cannot be a Schema object.'); } - - var keys = Object.keys(schemas); - var patternName = keys[0]; - var patternSchema = schemas[patternName]; - keys = keys.slice(1); - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var result = {}; - var patternValue = Schema.unpack(patternSchema, buffer, options, offsets); - result[patternName] = patternValue; - keys.every(function (key) { - if (patternValue === schemas[key][0]) { - result[key] = Schema.unpack(schemas[key][1], buffer, options, offsets); - return false; - } - return true; - }); - return result; - }, - pack: function _pack(value, options, buffer) { - var patternValue = value[patternName]; - Schema.pack(patternSchema, patternValue, options, buffer); - keys.every(function (key) { - if (patternValue === schemas[key][0]) { - Schema.pack(schemas[key][1], value[key], options, buffer); - return false; - } - return true; - }); - }, - name: 'cases{' + patternName + '}', - namespace: 'cases' - }); + if (!(patterns instanceof Array)) { + throw new Error('Parameter "patterns" must be a array.'); + } + /**/ + + var schemaCreator = function (value) { + for (var i = 0; i < patterns.length; i++) { + if (patterns[i][0] === value) { + return patterns[i][1]; + } + } + }; + schemaCreator.schema = 'cases(' + Schema.stringify(patterns) + ')'; + schemaCreator.namespace = 'cases'; + return schemaCreator; } Schema.register('cases', cases); /**/ diff --git a/src/schemas/cstring.js b/src/schemas/cstring.js index 9a97463..07c2f37 100644 --- a/src/schemas/cstring.js +++ b/src/schemas/cstring.js @@ -1,4 +1,4 @@ -module.exports = function defineCString(Schema) { +module.exports = function (Schema) { /**/ /** * 以零号字符结尾的字符串 @@ -34,7 +34,7 @@ module.exports = function defineCString(Schema) { Schema.pack('byte', 0, options, buffer); }, namespace: 'cstring', - name: 'cstring' + schema: 'cstring' }); Schema.register('cstring', cstring); Schema.register('pchar', cstring); diff --git a/src/schemas/depend.js b/src/schemas/depend.js new file mode 100644 index 0000000..2b1764e --- /dev/null +++ b/src/schemas/depend.js @@ -0,0 +1,51 @@ +module.exports = function (Schema) { + /**/ + /** + * 声明字段依赖结构 + * + * @param {string} field 字段名 + * @param {Function} schemaCreator 创建数据结构的方法 + * [[[ + * @param {Any} value 传递值 + * @return {Schema} 返回数据结构 + * function schemaCreator(value) {} + * ]]] + */ + function depend(field, schemaCreator) { + if (typeof field !== 'string') { + throw new Error('Parameter "field" must be a string.'); + } + if (typeof schemaCreator !== 'function') { + throw new Error('Parameter "field" must be a function.'); + } + + return new Schema({ + unpack: function _unpack(buffer, options, offsets) { + if (!options.$scope) { + throw new Error('Unpack must running in object.'); + } + var fieldValue = options.$scope[field]; + if (typeof fieldValue === 'undefined') { + throw new Error('Field "' + field + '" is undefined.'); + } + return Schema.unpack(schemaCreator(fieldValue), buffer, options, offsets); + }, + pack: function _pack(value, options, buffer) { + var fieldValue = options.$scope[field]; + if (typeof fieldValue === 'undefined') { + throw new Error('Field "' + field + '" is undefined.'); + } + Schema.pack(schemaCreator(fieldValue), value, options, buffer); + }, + schema: 'depend(' + [field, Schema.stringify(schemaCreator)] + ')', + namespace: 'depend' + }); + } + Schema.register('depend', depend); + + function dependArray(field, itemSchema) { + return depend(field, Schema.array(itemSchema)); + } + Schema.register('dependArray', dependArray); + /**/ +}; \ No newline at end of file diff --git a/src/schemas/dependArray.js b/src/schemas/dependArray.js deleted file mode 100644 index 47dd426..0000000 --- a/src/schemas/dependArray.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function defineDependArray(Schema) { - /**/ - function dependArray(field, itemSchema) { - if (typeof field !== 'string') { - throw new Error('Parameter "field" must be a string.'); - } - - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (!options.$scope) { - throw new Error('Unpack must running in object.'); - } - var length = options.$scope[field]; - if (typeof length !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - return Schema.unpack(Schema.staticArray(length, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - var length = options.$scope[field]; - if (typeof length !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - Schema.pack(Schema.array(length, itemSchema), value, options, buffer); - }, - name: 'depend array{' + field + '}', - namespace: 'dependArray' - }); - } - Schema.register('dependArray', dependArray); - /**/ -}; \ No newline at end of file diff --git a/src/schemas/dependString.js b/src/schemas/dependString.js deleted file mode 100644 index 64823db..0000000 --- a/src/schemas/dependString.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function defineDependString(Schema) { - /**/ - function dependString(field, itemSchema) { - if (typeof field !== 'string') { - throw new Error('Parameter "field" must be a string.'); - } - - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (!options.$scope) { - throw new Error('Unpack must running in object.'); - } - var size = options.$scope[field]; - if (typeof size !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - return Schema.unpack(Schema.staticString(size, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - var size = options.$scope[field]; - if (typeof size !== 'number') { - throw new Error('Field "' + field + '" must be a number.'); - } - Schema.pack(Schema.string(size), value, options, buffer); - }, - name: 'depend string{' + field + '}', - namespace: 'dependString' - }); - } - Schema.register('dependString', dependString); - /**/ -}; \ No newline at end of file diff --git a/src/schemas/dynamicArray.js b/src/schemas/dynamicArray.js deleted file mode 100644 index 0dd7621..0000000 --- a/src/schemas/dynamicArray.js +++ /dev/null @@ -1,104 +0,0 @@ -module.exports = function defineDynamicArray(Schema) { - /**/ - /** - * 声明指定长度的数组类型 - * - * @param {string|Schema} lengthSchema 长度类型 - * @param {string|Schema} itemSchema 元素类型 - * @return {Schema} 返回数据结构 - * @example 调用示例 1 - ```js - var _ = jpacks; - var _schema = _.dynamicArray(_.byte, _.byte); - var ab = _.pack(_schema, [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [4, 1, 2, 3, 4] - - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4] - ``` - * @example 调用示例 2 - ```js - var _ = jpacks; - var _schema = _.dynamicArray(_.word, _.byte); - var ab = _.pack(_schema, [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 0, 0, 4, 1, 2, 3, 4] - - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4] - ``` - */ - function dynamicArray(lengthSchema, itemSchema) { - lengthSchema = Schema.from(lengthSchema); - if (lengthSchema.namespace !== 'base') { - throw new Error('Parameter "lengthSchema" is not a numeric type.'); - } - if (!itemSchema) { - throw new Error('Parameter "itemSchema" is undefined.'); - } - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var length = Schema.unpack(lengthSchema, buffer, options, offsets); - return Schema.unpack(Schema.array(length, itemSchema), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - if (!value) { - Schema.pack(lengthSchema, 0, options, buffer); - } else { - Schema.pack(lengthSchema, value.length, options, buffer); - Schema.pack(Schema.array(value.length, itemSchema), value, options, buffer); - } - }, - name: 'dynamic array[..' + lengthSchema.name + '..]', - namespace: 'dynamicArray', - size: lengthSchema.size - }); - } - - Schema.register('dynamicArray', dynamicArray); - - function shortArray(schema) { - return dynamicArray('uint8', schema); - } - Schema.register('shortArray', shortArray); - - function smallArray(schema) { - return dynamicArray('uint16', schema); - } - Schema.register('smallArray', smallArray); - - function longArray(schema) { - return dynamicArray('uint32', schema); - } - Schema.register('longArray', longArray); - - // 'int8[.]' - shortArray - // 'int8[..]' - smallArray - // 'int8[....]' - longArray - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^(\w+)\[(\.|\..|\....)\]$/); - if (match) { - var baseSchema = Schema.from(match[1]); - if (baseSchema) { - var lengthSchema = 'uint16'; - switch (match[2]) { - case '.': - lengthSchema = 'uint8' - break; - case '....': - lengthSchema = 'uint32' - break; - } - var arraySchema = dynamicArray(lengthSchema, baseSchema); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - } - }); - /**/ -}; \ No newline at end of file diff --git a/src/schemas/dynamicString.js b/src/schemas/dynamicString.js deleted file mode 100644 index ccdf4dd..0000000 --- a/src/schemas/dynamicString.js +++ /dev/null @@ -1,107 +0,0 @@ -module.exports = function defineDynamicString(Schema) { - /**/ - /** - * 声明指定长度的字符串 - * - * @param {string|Schema} lengthSchema 长度类型 - * @return {Schema} 返回数据结构 - * @example 调用示例 - ```js - var _ = jpacks; - var _schema = _.dynamicString('int32'); - var ab = _.pack(_schema, '你好 World!'); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 0, 0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] - - console.log(_.unpack(_schema, u8a)); - // -> 你好 World! - ``` - */ - function dynamicString(lengthSchema) { - lengthSchema = Schema.from(lengthSchema); - if (!lengthSchema) { - throw new Error('Parameter "lengthSchema" is undefined.'); - } - if (lengthSchema.namespace !== 'base') { - throw new Error('Parameter "lengthSchema" is not a numeric type.'); - } - - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - var length = Schema.unpack(lengthSchema, buffer, options, offsets); - return Schema.unpack(Schema.string(length), buffer, options, offsets); - }, - pack: function _pack(value, options, buffer) { - if (!value) { - Schema.pack(lengthSchema, 0, options, buffer); - } else { - var bytes = Schema.stringBytes(value); - Schema.pack(lengthSchema, bytes.length, options, buffer); - Schema.pack(Schema.bytes(bytes.length), bytes, options, buffer); - } - }, - namespace: 'dynamicString', - name: 'string{..' + lengthSchema.name + '..}' - }); - } - Schema.register('dynamicString', dynamicString); - - /** - * 短字符串类型 - * - * @return {Schema} 返回数据结构 - * @example 调用示例 - ```js - var _ = jpacks; - var _schema = _.shortString; - var ab = _.pack(_schema, '你好 World!'); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] - - console.log(_.unpack(_schema, u8a)); - // -> 你好 World! - ``` - */ - Schema.register('shortString', dynamicString('uint8')); - - /** - * 长字符串类型 - * - * @return {Schema} 返回数据结构 - * @example 调用示例 - ```js - var _ = jpacks; - var _schema = _.smallString; - var ab = _.pack(_schema, '你好 World!'); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] - - console.log(_.unpack(_schema, u8a)); - // -> 你好 World! - ``` - */ - Schema.register('smallString', dynamicString('uint16')); - - /** - * 长字符串类型 - * - * @return {Schema} 返回数据结构 - * @example 调用示例 - ```js - var _ = jpacks; - var _schema = _.longString; - var ab = _.pack(_schema, '你好 World!'); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [0, 0, 0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] - - console.log(_.unpack(_schema, u8a)); - // -> 你好 World! - ``` - */ - Schema.register('longString', dynamicString('uint32')); - /**/ -}; \ No newline at end of file diff --git a/src/schemas/enums.js b/src/schemas/enums.js index b75954c..3140d4b 100644 --- a/src/schemas/enums.js +++ b/src/schemas/enums.js @@ -1,4 +1,4 @@ -module.exports = function defineEnum(Schema) { +module.exports = function (Schema) { /**/ /** * 定义一个枚举结构 @@ -52,7 +52,8 @@ module.exports = function defineEnum(Schema) { }; }, map: map, - name: 'enum {}' + schema: 'enum(' + [Schema.stringify(baseSchema), Schema.stringify(map)] + ')', + namespace: 'base' }); }; diff --git a/src/schemas/base.js b/src/schemas/number.js similarity index 96% rename from src/schemas/base.js rename to src/schemas/number.js index cd78c33..3b95b23 100644 --- a/src/schemas/base.js +++ b/src/schemas/number.js @@ -1,4 +1,4 @@ -module.exports = function defineArray(Schema) { +module.exports = function defineNumber(Schema) { /**/ /** * 基础类型 @@ -91,7 +91,7 @@ module.exports = function defineArray(Schema) { })('set' + item.type), size: item.size, name: name, - namespace: 'base', + namespace: 'number', array: item.array }); diff --git a/src/schemas/object.js b/src/schemas/object.js index 6c96aa5..7b51428 100644 --- a/src/schemas/object.js +++ b/src/schemas/object.js @@ -1,4 +1,4 @@ -module.exports = function defineObject(Schema) { +module.exports = function (Schema) { /**/ /** * 定义一个对象结构 @@ -6,20 +6,22 @@ module.exports = function defineObject(Schema) { * @param {object} schema 数据结构 * @return {Schema} 返回构建的数据结构 */ - function object(schema) { - if (typeof schema !== 'object') { + function object(objectSchema) { + if (typeof objectSchema !== 'object') { throw new Error('Parameter "schemas" must be a object type.'); } - if (schema instanceof Schema) { - return schema; + if (objectSchema instanceof Schema) { + return objectSchema; } + var names = Schema.stringify(objectSchema); + var keys = Object.keys(objectSchema); return new Schema({ unpack: function _unpack(buffer, options, offsets) { - var result = {}; + var result = new objectSchema.constructor(); var $scope = options.$scope; options.$scope = result; - Object.keys(schema).forEach(function (key) { - result[key] = Schema.unpack(schema[key], buffer, options, offsets); + keys.forEach(function (key) { + result[key] = Schema.unpack(objectSchema[key], buffer, options, offsets); }); options.$scope = $scope; return result; @@ -27,13 +29,14 @@ module.exports = function defineObject(Schema) { pack: function _pack(value, options, buffer) { var $scope = options.$scope; options.$scope = value; - Object.keys(schema).forEach(function (key) { - Schema.pack(schema[key], value[key], options, buffer); + keys.forEach(function (key) { + Schema.pack(objectSchema[key], value[key], options, buffer); }); options.$scope = $scope; }, - object: schema, - name: 'object {}' + object: objectSchema, + schema: 'object(' + names + ')', + namespace: 'object' }); }; diff --git a/src/schemas/staticArray.js b/src/schemas/staticArray.js deleted file mode 100644 index d12d49d..0000000 --- a/src/schemas/staticArray.js +++ /dev/null @@ -1,92 +0,0 @@ -module.exports = function defineArray(Schema) { - /**/ - /** - * 声明数组类型 - * - * @param {string|Schema} itemSchema 数组元素类型 - * @param {number} count 元素个数 - * @return {Schema} 返回数据结构 - * @example 调用示例 1 - ```js - var _ = jpacks; - var _schema = _.array(10, _.byte); - var ab = _.pack(_.array(10, _.byte), [1, 2, 3, 4]); - var u8a = new Uint8Array(ab); - console.log(u8a); - // -> [1, 2, 3, 4, 0, 0, 0, 0, 0, 0] - - console.log(_.unpack(_schema, u8a)); - // -> [1, 2, 3, 4, 0, 0, 0, 0, 0, 0] - ``` - */ - function array(count, itemSchema) { - if (typeof count !== 'number') { - var temp = count; - count = itemSchema; - itemSchema = temp; - } - var schema = Schema.from(itemSchema); - if (!schema) { - throw new Error('Schema "' + itemSchema + '" not define.'); - } - - var size = schema.size * count; - - return new Schema({ - unpack: function _unpack(buffer, options, offsets) { - if (schema.array && options.littleEndian) { - /* TypeArray littleEndian is true */ - var offset = offsets[0]; - offsets[0] += size; - return [].slice.apply(new schema.array(buffer, offset, count)); - } - - var result = []; - for (var i = 0; i < count; i++) { - result.push(Schema.unpack(schema, buffer, options, offsets)); - } - return result; - }, - pack: function _pack(value, options, buffer) { - if (schema.array && options.littleEndian) { - /* TypeArray littleEndian is true */ - var arrayBuffer = new ArrayBuffer(size); - var typeArray = new schema.array(arrayBuffer); - typeArray.set(value); - var uint8Array = new Uint8Array(arrayBuffer); - [].push.apply(buffer, uint8Array); - } - - for (var i = 0; i < count; i++) { - Schema.pack(schema, value[i], options, buffer); - } - }, - name: schema.name + '[' + count + ']', - size: size, - namespace: 'array' - }); - } - - Schema.register('array', array); - Schema.register('staticArray', array); - - function bytes(size) { - return array(size, 'uint8'); - } - Schema.register('bytes', bytes); - - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^(\w+)\[(\d+)\]$/); - if (match) { - var baseSchema = Schema.from(match[1]); - if (baseSchema) { - var arraySchema = array(parseInt(match[2]), baseSchema); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - } - }); - /**/ -}; \ No newline at end of file diff --git a/src/schemas/staticString.js b/src/schemas/string.js similarity index 59% rename from src/schemas/staticString.js rename to src/schemas/string.js index a3b35f9..4e13154 100644 --- a/src/schemas/staticString.js +++ b/src/schemas/string.js @@ -1,4 +1,4 @@ -module.exports = function defineStaticString(Schema) { +module.exports = function (Schema) { /**/ /** * 对字符串进行 utf8 编码 @@ -20,7 +20,6 @@ module.exports = function defineStaticString(Schema) { } ); } - /** * 对 utf8 字符串进行解码 * @@ -41,7 +40,6 @@ module.exports = function defineStaticString(Schema) { } ); } - /** * 将字符串转换为字节数组 * @@ -58,12 +56,11 @@ module.exports = function defineStaticString(Schema) { }); } } - Schema.stringBytes = stringBytes; /** * 声明指定长度的字符串 * - * @param {number} size 字节个数 + * @param {number|string|Schema} size 字节个数下标类型 * @return {Schema} 返回数据结构 */ function string(size) { @@ -79,23 +76,63 @@ module.exports = function defineStaticString(Schema) { pack: function _pack(value, options, buffer) { Schema.pack(schema, stringBytes(value), options, buffer); }, - namespace: 'staticString', - name: 'string{' + size + '}', + namespace: 'string', + name: 'string(' + Schema.stringify(size) + ')', size: size }); } - Schema.register('string', string); - Schema.register('staticString', string); - Schema.pushPattern(function (schema) { - if (typeof schema === 'string') { - var match = schema.match(/^string\{(\d+)\}$/); - if (match) { - var arraySchema = string(parseInt(match[1])); - Schema.register(schema, arraySchema); // 缓存 - return arraySchema; - } - } - }); + + /** + * 短字符串类型 + * + * @return {Schema} 返回数据结构 + * @example 调用示例 + ```js + var _ = jpacks; + var _schema = _.shortString; + var ab = _.pack(_schema, '你好 World!'); + var u8a = new Uint8Array(ab); + console.log(u8a); + // -> [13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] + console.log(_.unpack(_schema, u8a)); + // -> 你好 World! + ``` + */ + Schema.register('shortString', string('uint8')); + /** + * 长字符串类型 + * + * @return {Schema} 返回数据结构 + * @example 调用示例 + ```js + var _ = jpacks; + var _schema = _.smallString; + var ab = _.pack(_schema, '你好 World!'); + var u8a = new Uint8Array(ab); + console.log(u8a); + // -> [0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] + console.log(_.unpack(_schema, u8a)); + // -> 你好 World! + ``` + */ + Schema.register('smallString', string('uint16')); + /** + * 超长字符串类型 + * + * @return {Schema} 返回数据结构 + * @example 调用示例 + ```js + var _ = jpacks; + var _schema = _.longString; + var ab = _.pack(_schema, '你好 World!'); + var u8a = new Uint8Array(ab); + console.log(u8a); + // -> [0, 0, 0, 13, 228, 189, 160, 229, 165, 189, 32, 87, 111, 114, 108, 100, 33] + console.log(_.unpack(_schema, u8a)); + // -> 你好 World! + ``` + */ + Schema.register('longString', string('uint32')); /**/ }; \ No newline at end of file diff --git a/src/schemas/union.js b/src/schemas/union.js index 9035f82..947d2b5 100644 --- a/src/schemas/union.js +++ b/src/schemas/union.js @@ -1,4 +1,4 @@ -module.exports = function defineUnion(Schema) { +module.exports = function (Schema) { /**/ /** * 创建联合类型 @@ -64,7 +64,7 @@ module.exports = function defineUnion(Schema) { }, size: size, object: schemas, - name: 'union {}' + schema: 'union(' + Schema.stringify(size) + ')' }); } Schema.register('union', union); diff --git a/test/test.js b/test/test.js index 35376ee..8157d4e 100644 --- a/test/test.js +++ b/test/test.js @@ -14,15 +14,17 @@ describe('fixtures', function () { length: 'int32', note: jpacks.shortString }); - jpacks.register('CaseType', jpacks.cases({ + jpacks.register('CaseType', { type: 'uint8', - point: [1, 'Point'], - polar: [2, 'Polar'] - })); + data: jpacks.depend('type', jpacks.cases([ + [1, 'Point'], + [2, 'Polar'] + ])) + }); var value1 = { type: 1, - point: { + data: { x: 1, y: 2 } @@ -33,7 +35,7 @@ describe('fixtures', function () { var value3 = { type: 2, - polar: { + data: { angle: Math.PI, length: 2, note: '极坐标 1'