Skip to content

Instantly share code, notes, and snippets.

@unicomp21
Created April 24, 2023 12:11
Show Gist options
  • Save unicomp21/3e67673dbd9c86f0b89ac2f1c63b2bb7 to your computer and use it in GitHub Desktop.
Save unicomp21/3e67673dbd9c86f0b89ac2f1c63b2bb7 to your computer and use it in GitHub Desktop.
code and tests written by gpt-4, aka "gpt protocol buffers"
import {HexLengthDelimitedInt} from "./hexLengthDelimitedInt.impl";
function processSerializedString(input: string, offset: number): { length: number, value: string, nextOffset: number } {
const lengthResult = HexLengthDelimitedInt.deserialize(input, offset);
offset = lengthResult.nextOffset;
const value = input.slice(offset, offset + lengthResult.number);
offset += lengthResult.number;
return {length: lengthResult.number, value, nextOffset: offset};
}
export function serializeTagValue(tag: string, value: string): string {
const serializedTag = HexLengthDelimitedInt.serialize(tag.length) + tag;
const serializedValue = HexLengthDelimitedInt.serialize(value.length) + value;
return serializedTag + serializedValue + '\n';
}
export function deserializeTagValue(input: string, offset: number): { tag: string, value: string, nextOffset: number } {
const tagResult = processSerializedString(input, offset);
offset = tagResult.nextOffset;
const valueResult = processSerializedString(input, offset);
offset = valueResult.nextOffset;
return {tag: tagResult.value, value: valueResult.value, nextOffset: offset};
}
function containsOnlyDecimalDigits(input: string): boolean {
for (const char of input) {
if (char < '0' || char > '9') {
return false;
}
}
return true;
}
export interface DeserializationResult {
number: number;
nextOffset: number;
}
export class HexLengthDelimitedInt {
static deserialize(input: string, offset = 0): DeserializationResult {
if (input[offset] !== '(') {
throw new Error("Invalid input format");
}
let nextOffset = -1;
for (let i = offset + 1; i < input.length; i++) {
if (input[i] === ')') {
nextOffset = i;
break;
}
}
if (nextOffset === -1) {
throw new Error("Invalid input format");
}
const length = parseInt(input.slice(offset + 1, offset + 2), 16);
if (isNaN(length) || length < 0 || length > 15) {
throw new Error("Invalid input length");
}
const dec = input.slice(offset + 2, nextOffset);
if (!containsOnlyDecimalDigits(dec)) {
throw new Error("Invalid decimal digits in length-delimited integer");
}
const number = parseInt(dec, 10);
if (!Number.isInteger(number)) {
throw new Error("Deserialized number is not an integer");
}
return {number, nextOffset: nextOffset + 1};
}
static serialize(number: number): string {
if (!Number.isInteger(number)) {
throw new Error("Input number is not an integer");
}
const dec = number.toString(10);
const length = dec.length;
if (length > 15) {
throw new Error("Number too large to serialize");
}
return '(' + length.toString(16) + dec + ')';
}
}
import {HexLengthDelimitedInt} from "./hexLengthDelimitedInt.impl";
describe('HexLengthDelimitedInt', () => {
test('should serialize and deserialize an integer correctly', () => {
const number = 12345;
const serialized = HexLengthDelimitedInt.serialize(number);
expect(serialized).toBe('(512345)');
const {number: deserialized, nextOffset} = HexLengthDelimitedInt.deserialize(serialized);
expect(deserialized).toBe(number);
expect(nextOffset).toBe(8);
});
test('should throw an error when serializing a non-integer', () => {
const number = 123.45;
expect(() => HexLengthDelimitedInt.serialize(number)).toThrow('Input number is not an integer');
});
test('should throw an error when deserializing a non-integer', () => {
const input = '(51234.5)';
expect(() => HexLengthDelimitedInt.deserialize(input)).toThrow('Invalid decimal digits in length-delimited integer');
});
test('should throw an error when deserializing an invalid input format', () => {
const input1 = '512345)';
expect(() => HexLengthDelimitedInt.deserialize(input1)).toThrow('Invalid input format');
const input2 = '(512345';
expect(() => HexLengthDelimitedInt.deserialize(input2)).toThrow('Invalid input format');
});
test('should throw an error when deserializing an input with invalid length', () => {
const input = '(g123456123)';
expect(() => HexLengthDelimitedInt.deserialize(input)).toThrow('Invalid input length');
});
test('should throw an error when serializing a number too large to serialize', () => {
const number = 0xFFFFFFFFFFFFFFFF;
expect(() => HexLengthDelimitedInt.serialize(number)).toThrow('Number too large to serialize');
});
test('should deserialize multiple numbers from the same input string', () => {
const input = "(512345)(6123456)";
const {number: deserialized1, nextOffset} = HexLengthDelimitedInt.deserialize(input);
expect(deserialized1).toBe(12345);
const {number: deserialized2} = HexLengthDelimitedInt.deserialize(input, nextOffset);
expect(deserialized2).toBe(123456);
});
});
import {deserializeTagValue, serializeTagValue} from "./common.impl";
import {HexLengthDelimitedInt} from "./hexLengthDelimitedInt.impl";
export class TagValueArray {
public static serialize(array: [string, string][]): string {
let result = '';
result += HexLengthDelimitedInt.serialize(array.length) + '\n';
for (let i = 0; i < array.length; i++) {
const [tag, value] = array[i];
result += serializeTagValue(tag, value);
}
return result;
}
static serializeNoLength(tagValueArray: Array<[string, string]>): string {
let message = '';
for (const [tag, value] of tagValueArray) {
message += serializeTagValue(tag, value);
}
return message;
}
public static serializeLength(array: [string, string][]): number {
let length = 0;
length += HexLengthDelimitedInt.serialize(array.length).length + 1; // Add 1 for the newline character
for (let i = 0; i < array.length; i++) {
const [tag, value] = array[i];
length += serializeTagValue(tag, value).length;
}
return length;
}
private static processTagValuePairs(input: string, count: number, offset: number): Array<[string, string]> {
const tagValueArray: Array<[string, string]> = [];
for (let i = 0; i < count; i++) {
const { tag, value, nextOffset } = deserializeTagValue(input, offset);
offset = nextOffset;
tagValueArray.push([tag, value]);
if (i < count - 1 && input[offset] !== '\n') {
throw new Error("Invalid input format: missing newline character after tag-value pair");
}
offset++;
}
return tagValueArray;
}
static deserialize(input: string): { array: Array<[string, string]>, nextOffset: number } {
const countResult = HexLengthDelimitedInt.deserialize(input);
const count = countResult.number;
let offset = countResult.nextOffset;
if (input[offset] !== '\n') {
throw new Error("Invalid input format: missing newline character after count");
}
offset++;
const array = TagValueArray.processTagValuePairs(input, count, offset);
return { array, nextOffset: offset };
}
static deserializeNoLength(input: string, count: number): Array<[string, string]> {
return TagValueArray.processTagValuePairs(input, count, 0);
}
static isEmpty(tagValueArray: Array<[string, string]>): boolean {
return tagValueArray.length === 0;
}
static hasTag(tagValueArray: Array<[string, string]>, tag: string): boolean {
return tagValueArray.some(([t, _]) => t === tag);
}
}
import {TagValueArray} from "./tagValueArray.impl";
import {HexLengthDelimitedInt} from "./hexLengthDelimitedInt.impl";
describe('TagValueArray', () => {
const testCases = [
{
name: 'Empty array',
input: [] as [string, string][],
},
{
name: 'Single tag-value pair',
input: [['tag1', 'value1']] as [string, string][],
},
{
name: 'Multiple tag-value pairs',
input: [
['tag1', 'value1'],
['tag2', 'value2'],
['tag3', 'value3'],
] as [string, string][],
},
{
name: 'Long tag and value',
input: [['tagWithLongName', 'valueWithLongContent']] as [string, string][],
},
];
testCases.forEach(testCase => {
test(`Serialize and deserialize: ${testCase.name}`, () => {
const serialized = TagValueArray.serialize(testCase.input);
const deserialized = TagValueArray.deserialize(serialized);
expect(deserialized.array).toEqual(testCase.input);
});
});
test('Serialize and deserialize: Empty tag or value', () => {
const input: [string, string][] = [['tag1', ''], ['', 'value2']];
const serialized = TagValueArray.serialize(input);
const deserialized = TagValueArray.deserialize(serialized);
expect(deserialized.array).toEqual(input);
});
test('Serialize and deserialize: Tag or value containing newline characters', () => {
const input: [string, string][] = [['tag\n1', 'value\n1'], ['tag2', 'value\n2']];
const serialized = TagValueArray.serialize(input);
const deserialized = TagValueArray.deserialize(serialized);
expect(deserialized.array).toEqual(input);
});
test('Serialize and deserialize: Tag or value containing special characters', () => {
const input: [string, string][] = [['täg1', 'vàlué1'], ['t🚀g2', 'v🌍luè2']];
const serialized = TagValueArray.serialize(input);
const deserialized = TagValueArray.deserialize(serialized);
expect(deserialized.array).toEqual(input);
});
test('SerializeLength', () => {
const input: [string, string][] = [['tag1', 'value1'], ['tag2', 'value2'], ['tag3', 'value3']];
const length = TagValueArray.serializeLength(input);
const serialized = TagValueArray.serialize(input);
expect(length).toEqual(serialized.length);
});
});
import {TagValueArray} from "./tagValueArray.impl";
export class TagValueMessage {
private tagValueMap: Map<string, string>;
constructor() {
this.tagValueMap = new Map<string, string>();
}
set(tag: string, value: string): void {
this.tagValueMap.set(tag, value);
}
get(tag: string): string | undefined {
return this.tagValueMap.get(tag);
}
has(tag: string): boolean {
return this.tagValueMap.has(tag);
}
delete(tag: string): boolean {
return this.tagValueMap.delete(tag);
}
clear(): void {
this.tagValueMap.clear();
}
serialize(): string {
const tagValueArray: Array<[string, string]> = [];
for (const [tag, value] of this.tagValueMap.entries()) {
tagValueArray.push([tag, value]);
}
return TagValueArray.serialize(tagValueArray);
}
deserialize(input: string): number {
const { array, nextOffset } = TagValueArray.deserialize(input);
this.tagValueMap.clear();
for (const [tag, value] of array) {
this.set(tag, value);
}
return nextOffset;
}
}
import {TagValueMessage} from "./tagValueMessage.impl";
describe('TagValueMessage', () => {
let message: TagValueMessage;
beforeEach(() => {
message = new TagValueMessage();
});
test('set, get, has, and delete methods', () => {
expect(message.has('tag1')).toBe(false);
expect(message.get('tag1')).toBeUndefined();
message.set('tag1', 'value1');
expect(message.has('tag1')).toBe(true);
expect(message.get('tag1')).toBe('value1');
message.set('tag2', 'value2');
expect(message.has('tag2')).toBe(true);
expect(message.get('tag2')).toBe('value2');
expect(message.delete('tag1')).toBe(true);
expect(message.has('tag1')).toBe(false);
expect(message.get('tag1')).toBeUndefined();
expect(message.delete('nonexistent')).toBe(false);
});
test('clear method', () => {
message.set('tag1', 'value1');
message.set('tag2', 'value2');
message.clear();
expect(message.has('tag1')).toBe(false);
expect(message.get('tag1')).toBeUndefined();
expect(message.has('tag2')).toBe(false);
expect(message.get('tag2')).toBeUndefined();
});
test('serialize and deserialize methods', () => {
message.set('tag1', 'value1');
message.set('tag2', 'value2');
const serialized = message.serialize();
const deserializedMessage = new TagValueMessage();
deserializedMessage.deserialize(serialized);
expect(deserializedMessage.has('tag1')).toBe(true);
expect(deserializedMessage.get('tag1')).toBe('value1');
expect(deserializedMessage.has('tag2')).toBe(true);
expect(deserializedMessage.get('tag2')).toBe('value2');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment