From 3c69b8f05e0dfe6337f4b6dc5da218e272d1547e Mon Sep 17 00:00:00 2001 From: Casper Warden <216465704+casperwardensl@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:25:34 +0000 Subject: [PATCH] Actually, the message format is LLSD notation not python (d'oh) --- lib/classes/ObjectStoreLite.ts | 15 +- .../commands/CommunicationsCommands.ts | 21 ++ lib/classes/commands/FriendCommands.ts | 3 + lib/classes/commands/GroupCommands.ts | 9 + lib/classes/llsd/LLSDArray.ts | 53 +++ lib/classes/llsd/LLSDMap.ts | 115 +++++++ lib/classes/llsd/LLSDNotationParser.spec.ts | 204 +++++++++++ lib/classes/llsd/LLSDNotationParser.ts | 320 ++++++++++++++++++ .../PythonObject.ts => llsd/LLSDObject.ts} | 2 +- lib/classes/llsd/LLSDToken.ts | 12 + lib/classes/llsd/LLSDTokenContainer.ts | 9 + lib/classes/llsd/LLSDTokenGenerator.ts | 3 + lib/classes/llsd/LLSDTokenSpec.ts | 7 + lib/classes/llsd/LLSDTokenType.ts | 23 ++ lib/classes/llsd/LLSDType.ts | 4 + lib/classes/python/PythonDict.ts | 109 ------ lib/classes/python/PythonList.ts | 69 ---- lib/classes/python/PythonParser.spec.ts | 148 -------- lib/classes/python/PythonParser.ts | 168 --------- lib/classes/python/PythonSet.ts | 70 ---- lib/classes/python/PythonToken.ts | 7 - lib/classes/python/PythonTokenContainer.ts | 7 - lib/classes/python/PythonTokenType.ts | 21 -- lib/classes/python/PythonTuple.ts | 70 ---- lib/classes/python/PythonType.ts | 3 - package.json | 2 +- 26 files changed, 792 insertions(+), 682 deletions(-) create mode 100644 lib/classes/llsd/LLSDArray.ts create mode 100644 lib/classes/llsd/LLSDMap.ts create mode 100644 lib/classes/llsd/LLSDNotationParser.spec.ts create mode 100644 lib/classes/llsd/LLSDNotationParser.ts rename lib/classes/{python/PythonObject.ts => llsd/LLSDObject.ts} (79%) create mode 100644 lib/classes/llsd/LLSDToken.ts create mode 100644 lib/classes/llsd/LLSDTokenContainer.ts create mode 100644 lib/classes/llsd/LLSDTokenGenerator.ts create mode 100644 lib/classes/llsd/LLSDTokenSpec.ts create mode 100644 lib/classes/llsd/LLSDTokenType.ts create mode 100644 lib/classes/llsd/LLSDType.ts delete mode 100644 lib/classes/python/PythonDict.ts delete mode 100644 lib/classes/python/PythonList.ts delete mode 100644 lib/classes/python/PythonParser.spec.ts delete mode 100644 lib/classes/python/PythonParser.ts delete mode 100644 lib/classes/python/PythonSet.ts delete mode 100644 lib/classes/python/PythonToken.ts delete mode 100644 lib/classes/python/PythonTokenContainer.ts delete mode 100644 lib/classes/python/PythonTokenType.ts delete mode 100644 lib/classes/python/PythonTuple.ts delete mode 100644 lib/classes/python/PythonType.ts diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index dcb6bf4..e09263a 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -38,9 +38,8 @@ import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; import { Avatar } from './public/Avatar'; import Timer = NodeJS.Timer; import { GenericStreamingMessageMessage } from './messages/GenericStreamingMessage'; -import { PythonParser } from './python/PythonParser'; -import { PythonDict } from './python/PythonDict'; -import { PythonList } from './python/PythonList'; +import { LLSDNotationParser } from './llsd/LLSDNotationParser'; +import { LLSDMap } from './llsd/LLSDMap'; export class ObjectStoreLite implements IObjectStore { @@ -85,12 +84,12 @@ export class ObjectStoreLite implements IObjectStore const genMsg = packet.message as GenericStreamingMessageMessage; if (genMsg.MethodData.Method === 0x4175) { - // Whoever decided to use python notation for this is a psychopath - const result = PythonParser.parse(genMsg.DataBlock.Data.toString('utf-8')); - if (result instanceof PythonDict) + // LLSD Notation format + const result = LLSDNotationParser.parse(genMsg.DataBlock.Data.toString('utf-8')); + if (result instanceof LLSDMap) { const arr = result.get('te'); - if (arr instanceof PythonList) + if (Array.isArray(arr)) { if (arr.length === 0) { @@ -98,7 +97,7 @@ export class ObjectStoreLite implements IObjectStore } // TODO: figure out what to do with this.. - // console.log(JSON.stringify(result, null, 4)); + console.log(JSON.stringify(result, null, 4)); } } } diff --git a/lib/classes/commands/CommunicationsCommands.ts b/lib/classes/commands/CommunicationsCommands.ts index a97ad49..51f8140 100644 --- a/lib/classes/commands/CommunicationsCommands.ts +++ b/lib/classes/commands/CommunicationsCommands.ts @@ -68,6 +68,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(itemOrFolder.name), BinaryBucket: bucket }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -99,6 +102,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(message), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -223,6 +229,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(''), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -254,6 +263,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(''), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -407,6 +419,9 @@ export class CommunicationsCommands extends CommandsBase Message: Buffer.allocUnsafe(0), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; this.agent.deleteChatSession(groupID); const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable); return this.circuit.waitForAck(sequenceNo, 10000); @@ -445,6 +460,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(message), BinaryBucket: Utils.StringToBuffer('') }; + im.EstateBlock = { + EstateID: 0 + }; circuit.sendMessage(im, PacketFlags.Reliable); await Utils.waitOrTimeOut(this.currentRegion.clientEvents.onGroupChatSessionJoin, 10000, (event: GroupChatSessionJoinEvent) => { @@ -514,6 +532,9 @@ export class CommunicationsCommands extends CommandsBase Message: Utils.StringToBuffer(message), BinaryBucket: Utils.StringToBuffer('') }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); await this.circuit.waitForAck(sequenceNo, 10000); diff --git a/lib/classes/commands/FriendCommands.ts b/lib/classes/commands/FriendCommands.ts index aab8dae..1e9b0f1 100644 --- a/lib/classes/commands/FriendCommands.ts +++ b/lib/classes/commands/FriendCommands.ts @@ -307,6 +307,9 @@ export class FriendCommands extends CommandsBase Message: Utils.StringToBuffer(message), BinaryBucket: Utils.StringToBuffer('') }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable); return await this.circuit.waitForAck(sequenceNo, 10000); } diff --git a/lib/classes/commands/GroupCommands.ts b/lib/classes/commands/GroupCommands.ts index 52d932c..09d0e8f 100644 --- a/lib/classes/commands/GroupCommands.ts +++ b/lib/classes/commands/GroupCommands.ts @@ -50,6 +50,9 @@ export class GroupCommands extends CommandsBase Message: Utils.StringToBuffer(subject + '|' + message), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -137,6 +140,9 @@ export class GroupCommands extends CommandsBase Message: Utils.StringToBuffer(''), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } @@ -164,6 +170,9 @@ export class GroupCommands extends CommandsBase Message: Utils.StringToBuffer(''), BinaryBucket: Buffer.allocUnsafe(0) }; + im.EstateBlock = { + EstateID: 0 + }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); return await circuit.waitForAck(sequenceNo, 10000); } diff --git a/lib/classes/llsd/LLSDArray.ts b/lib/classes/llsd/LLSDArray.ts new file mode 100644 index 0000000..f566442 --- /dev/null +++ b/lib/classes/llsd/LLSDArray.ts @@ -0,0 +1,53 @@ +import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import { LLSDTokenType } from './LLSDTokenType'; +import { LLSDNotationParser } from './LLSDNotationParser'; +import { LLSDType } from './LLSDType'; + +export class LLSDArray +{ + public static parse(gen: LLSDTokenGenerator): LLSDType[] + { + const arr: LLSDType[] = []; + let value: LLSDType | undefined = undefined; + while (true) + { + const token = gen(); + if (token === undefined) + { + throw new Error('Unexpected end of input in array'); + } + switch (token.type) + { + case LLSDTokenType.WHITESPACE: + { + continue; + } + case LLSDTokenType.ARRAY_END: + { + if (value !== undefined) + { + arr.push(value); + } + return arr; + } + case LLSDTokenType.COMMA: + { + if (value === undefined) + { + throw new Error('Expected value before comma'); + } + arr.push(value); + value = undefined; + continue; + } + } + + if (value !== undefined) + { + throw new Error('Comma or end brace expected'); + } + + value = LLSDNotationParser.parseValueToken(gen, token); + } + } +} diff --git a/lib/classes/llsd/LLSDMap.ts b/lib/classes/llsd/LLSDMap.ts new file mode 100644 index 0000000..e2b9260 --- /dev/null +++ b/lib/classes/llsd/LLSDMap.ts @@ -0,0 +1,115 @@ +import { LLSDObject } from './LLSDObject'; +import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import { LLSDTokenType } from './LLSDTokenType'; +import { LLSDNotationParser } from './LLSDNotationParser'; +import { LLSDType } from './LLSDType'; + +export class LLSDMap extends LLSDObject +{ + public data: Map = new Map(); + + public static parse(gen: LLSDTokenGenerator): LLSDMap + { + const m = new LLSDMap(); + let expectsKey = true + let key: LLSDType | undefined = undefined; + let value: LLSDType | undefined = undefined; + while (true) + { + const token = gen(); + if (token === undefined) + { + throw new Error('Unexpected end of input in map'); + } + switch (token.type) + { + case LLSDTokenType.WHITESPACE: + { + continue; + } + case LLSDTokenType.MAP_END: + { + if (expectsKey) + { + throw new Error('Unexpected end of map'); + } + if (key !== undefined && value !== undefined) + { + m.data.set(key, value); + } + else if (m.data.size > 0) + { + throw new Error('Expected value before end of map'); + } + return m; + } + case LLSDTokenType.COLON: + { + if (!expectsKey) + { + throw new Error('Unexpected symbol: :'); + } + if (key === undefined) + { + throw new Error('Empty key not allowed'); + } + expectsKey = false; + continue; + } + case LLSDTokenType.COMMA: + { + if (expectsKey) + { + throw new Error('Empty map entry not allowed'); + } + if (value === undefined) + { + throw new Error('Empty map value not allowed'); + } + if (key !== undefined) + { + m.data.set(key, value); + } + key = undefined; + value = undefined; + expectsKey = true; + continue; + } + } + + if (expectsKey && key !== undefined) + { + throw new Error('Colon expected'); + } + else if (value !== undefined) + { + throw new Error('Comma or end brace expected'); + } + + const val = LLSDNotationParser.parseValueToken(gen, token); + if (expectsKey) + { + key = val; + } + else + { + value = val; + } + } + } + + get length(): number + { + return Object.keys(this.data).length; + } + + public get(key: LLSDType): LLSDType | undefined + { + return this.data.get(key); + } + + public toJSON(): unknown + { + return Object.fromEntries(this.data); + } +} diff --git a/lib/classes/llsd/LLSDNotationParser.spec.ts b/lib/classes/llsd/LLSDNotationParser.spec.ts new file mode 100644 index 0000000..73a11ee --- /dev/null +++ b/lib/classes/llsd/LLSDNotationParser.spec.ts @@ -0,0 +1,204 @@ +import * as assert from 'assert'; +import { LLSDNotationParser } from './LLSDNotationParser'; +import { LLSDMap } from './LLSDMap'; +import { UUID } from '../UUID'; + +describe('LLSDNotationParser', () => +{ + describe('parse', () => + { + it('can parse a complex LLSD Notation document', () => + { + const notationDoc = `{ + 'nested_map': { + 'nested_again': { + 'array': [ + i0, + 'string', + "string2", + [ + "another", + "array", + r4.3, + i22, + !, + { + 'isThis': i42 + } + ] + ] + } + }, + 'undef': !, + 'booleans': [ + true, + false, + 1, + 0, + T, + F, + t, + f, + TRUE, + FALSE + ], + 'integer': i69, + 'negInt': i-69, + 'real': r3.141, + 'realNeg': r-3.141, + 'uuid': ufb54f6b1-8120-40c9-9aa3-f9abef1a168f, + 'string1': "gday\\"mate", + 'string2': 'gday\\'mate', + 'string3': s(11)"hello"there", + 'uri': l"https://secondlife.com/", + 'date': d"2023-11-10T13:32:32.93Z", + 'binary': b64"amVsbHlmaXNo", + 'binary2': b16"6261636F6E62697473", + 'binary3': b(32)"KÚ~¬\béGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/" + }`; + const parsed = LLSDNotationParser.parse(notationDoc); + if (!(parsed instanceof LLSDMap)) + { + assert('Parsed document is not a map'); + return; + } + + const nested = parsed.get('nested_map'); + if (nested instanceof LLSDMap) + { + const nestedAgain = nested.get('nested_again'); + if (nestedAgain instanceof LLSDMap) + { + const arr = nestedAgain.get('array'); + if (!Array.isArray(arr)) + { + assert(false, 'Nested array is not an array'); + } + else + { + assert.equal(arr.length, 4); + assert.equal(arr[0], 0); + assert.equal(arr[1], 'string'); + assert.equal(arr[2], 'string2'); + const nestedAgainArr = arr[3]; + if (!Array.isArray(nestedAgainArr)) + { + assert(false, 'Nested again array is not an array'); + } + else + { + assert.equal(nestedAgainArr.length, 6); + assert.equal(nestedAgainArr[0], 'another'); + assert.equal(nestedAgainArr[1], 'array'); + assert.equal(nestedAgainArr[2], 4.3); + assert.equal(nestedAgainArr[3], 22); + assert.equal(nestedAgainArr[4], null); + const thirdNestedMap = nestedAgainArr[5]; + if (thirdNestedMap instanceof LLSDMap) + { + assert.equal(thirdNestedMap.get('isThis'), 42); + } + else + { + assert(false, 'Third nested map is not a map'); + } + } + } + } + else + { + assert(false, 'NestedAgain is not a map'); + } + } + else + { + assert(false, 'Nested is not a map'); + } + assert.equal(parsed.get('undef'), null); + const bools = parsed.get('booleans'); + if (!Array.isArray(bools)) + { + assert(false, 'Booleans array is not bools'); + } + else + { + assert.equal(bools.length, 10); + assert.equal(bools[0], true); + assert.equal(bools[1], false); + assert.equal(bools[2], true); + assert.equal(bools[3], false); + assert.equal(bools[4], true); + assert.equal(bools[5], false); + assert.equal(bools[6], true); + assert.equal(bools[7], false); + assert.equal(bools[8], true); + assert.equal(bools[9], false); + } + + /* + 'string1': "gday\"mate", + 'string2': 'gday\'mate', + 'string3': s(11)"hello"there", + 'uri': l"https://secondlife.com/", + 'date': d"2023-11-10T13:32:32.93Z", + 'binary': b64"amVsbHlmaXNo", + 'binary2': b16"6261636F6E62697473", + 'binary3': b(32)"KÚ~¬éGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/" + */ + assert.equal(parsed.get('integer'), 69); + assert.equal(parsed.get('negInt'), -69); + assert.equal(parsed.get('real'), 3.141); + assert.equal(parsed.get('realNeg'), -3.141); + const u = parsed.get('uuid'); + if (u instanceof UUID) + { + assert.equal(u.equals('fb54f6b1-8120-40c9-9aa3-f9abef1a168f'), true); + } + else + { + assert(false, 'UUID is not a UUID'); + } + assert.equal(parsed.get('string1'), 'gday"mate'); + assert.equal(parsed.get('string2'), 'gday\'mate'); + assert.equal(parsed.get('string3'), 'hello"there'); + assert.equal(parsed.get('uri'), 'https://secondlife.com/'); + const d = parsed.get('date'); + if (d instanceof Date) + { + assert.equal(d.getTime(), 1699623152930); + } + else + { + assert(false, 'Date entry is not a date'); + } + const buf1 = parsed.get('binary'); + if (buf1 instanceof Buffer) + { + assert.equal(buf1.toString('utf-8'), 'jellyfish'); + } + else + { + assert(false, 'Buf1 is not a buffer'); + } + const buf2 = parsed.get('binary2'); + if (buf2 instanceof Buffer) + { + assert.equal(buf2.toString('utf-8'), 'baconbits'); + } + else + { + assert(false, 'Buf2 is not a buffer'); + } + const buf3 = parsed.get('binary3'); + if (buf3 instanceof Buffer) + { + assert.equal(buf3.equals(Buffer.from('KÚ~¬\béGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/', 'binary')), true); + } + else + { + assert(false, 'Buf3 is not a buffer'); + } + }); + }); +}); + diff --git a/lib/classes/llsd/LLSDNotationParser.ts b/lib/classes/llsd/LLSDNotationParser.ts new file mode 100644 index 0000000..3d9225a --- /dev/null +++ b/lib/classes/llsd/LLSDNotationParser.ts @@ -0,0 +1,320 @@ +import { LLSDTokenType } from './LLSDTokenType'; +import { LLSDType } from './LLSDType'; +import { LLSDTokenSpec } from './LLSDTokenSpec'; +import { LLSDToken } from './LLSDToken'; +import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import { LLSDMap } from './LLSDMap'; +import { UUID } from '../UUID'; +import { LLSDArray } from './LLSDArray'; + +export class LLSDNotationParser +{ + private static tokenSpecs: LLSDTokenSpec[] = + [ + { regex: /^\s+/, type: LLSDTokenType.WHITESPACE }, + { regex: /^!/, type: LLSDTokenType.NULL }, + { regex: /^\{/, type: LLSDTokenType.MAP_START }, + { regex: /^}/, type: LLSDTokenType.MAP_END }, + { regex: /^:/, type: LLSDTokenType.COLON }, + { regex: /^,/, type: LLSDTokenType.COMMA }, + { regex: /^\[/, type: LLSDTokenType.ARRAY_START }, + { regex: /^]/, type: LLSDTokenType.ARRAY_END }, + { regex: /^(?:true|false|TRUE|FALSE|1|0|T|F|t|f)/, type: LLSDTokenType.BOOLEAN }, + { regex: /^i(-?[0-9]+)/, type: LLSDTokenType.INTEGER }, + { regex: /^r(-?[0-9.]+)/, type: LLSDTokenType.REAL }, + { regex: /^u([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/, type: LLSDTokenType.UUID }, + { regex: /^'([^'\\]*(?:\\.[^'\\\n]*)*)'/, type: LLSDTokenType.STRING_FIXED_SINGLE }, + { regex: /^"([^"\\]*(?:\\.[^"\\\n]*)*)"/, type: LLSDTokenType.STRING_FIXED_DOUBLE }, + { regex: /^s\(([0-9]+)\)"/, type: LLSDTokenType.STRING_DYNAMIC_START }, + { regex: /^l"([^"]*?)"/, type: LLSDTokenType.URI }, + { regex: /^d"([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{2}Z)"/, type: LLSDTokenType.DATE }, + { regex: /^b[0-9]{2}"[0-9a-zA-Z+\/=]*?"/, type: LLSDTokenType.BINARY_STATIC }, + { regex: /^b\(([0-9]+)\)"/, type: LLSDTokenType.BINARY_DYNAMIC_START } + ]; + + private static* tokenize(inpt: string): Generator + { + const dataContainer = { + input: inpt, + index: 0 + } + while (dataContainer.index < dataContainer.input.length) + { + const currentInput = dataContainer.input.slice(dataContainer.index); + + if (currentInput.length === 0) + { + return; // End of input + } + + let matched = false; + for (const { regex, type } of this.tokenSpecs) + { + const tokenMatch = currentInput.match(regex); + if (tokenMatch) + { + matched = true; + let value = tokenMatch[0]; + if (tokenMatch.length > 1) + { + value = tokenMatch[tokenMatch.length - 1]; + } + dataContainer.index += tokenMatch[0].length; // Move past this token + + yield { type, value, rawValue: tokenMatch[0], dataContainer }; + break; + } + } + if (!matched) + { + dataContainer.index++; + yield { type: LLSDTokenType.UNKNOWN, value: dataContainer.input[dataContainer.index - 1], rawValue: dataContainer.input[dataContainer.index - 1], dataContainer }; + } + } + } + + public static parseValueToken(gen: LLSDTokenGenerator, initialToken?: LLSDToken): LLSDType + { + while (true) + { + let t: LLSDToken | undefined; + if (initialToken !== undefined) + { + t = initialToken; + initialToken = undefined; + } + else + { + t = gen(); + if (t === undefined) + { + throw new Error('Unexpected end of input'); + } + } + switch (t.type) + { + case LLSDTokenType.UNKNOWN: + { + throw new Error('Unexpected token: ' + t.value); + } + case LLSDTokenType.WHITESPACE: + { + continue; + } + case LLSDTokenType.NULL: + { + return null; + } + case LLSDTokenType.BOOLEAN: + { + return t.value === 'true' || t.value === 'TRUE' || t.value === 'T' || t.value === 't' || t.value === '1'; + } + case LLSDTokenType.INTEGER: + { + return parseInt(t.value, 10); + } + case LLSDTokenType.REAL: + { + return parseFloat(t.value); + } + case LLSDTokenType.UUID: + { + return new UUID(t.value); + } + case LLSDTokenType.STRING_FIXED_SINGLE: + { + return t.value.replace(/\\'/, '\'') + .replace(/\\\\/g, '\\'); + } + case LLSDTokenType.STRING_FIXED_DOUBLE: + { + return t.value.replace(/\\"/, '"') + .replace(/\\\\/g, '\\'); + } + case LLSDTokenType.URI: + { + return t.value; + } + case LLSDTokenType.DATE: + { + return new Date(t.value); + } + case LLSDTokenType.BINARY_STATIC: + { + const b = t.value.match(/^b([0-9]{2})"([0-9a-zA-Z+\/=]*?)"/); + if (b === null || b.length < 3) + { + throw new Error('Invalid BINARY_STATIC'); + } + const base = parseInt(b[1], 10); + if (base !== 16 && base !== 64) + { + throw new Error('Unsupported base ' + String(base)); + } + return Buffer.from(b[2], base === 64 ? 'base64' : 'hex'); + } + case LLSDTokenType.STRING_DYNAMIC_START: + { + const length = parseInt(t.value, 10); + const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); + t.dataContainer.index += length; + if (t.dataContainer.input[t.dataContainer.index] !== '"') + { + throw new Error('Expected " at end of dynamic string') + } + t.dataContainer.index += 1; + return s; + } + case LLSDTokenType.BINARY_DYNAMIC_START: + { + const length = parseInt(t.value, 10); + const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); + t.dataContainer.index += length; + if (t.dataContainer.input[t.dataContainer.index] !== '"') + { + throw new Error('Expected " at end of dynamic binary string') + } + t.dataContainer.index += 1; + return Buffer.from(s, 'binary'); + } + case LLSDTokenType.MAP_START: + { + return LLSDMap.parse(gen); + } + case LLSDTokenType.ARRAY_START: + { + return LLSDArray.parse(gen); + } + } + } + } + + public static parse(input: string): LLSDType + { + const generator = this.tokenize(input); + const getToken: LLSDTokenGenerator = (): LLSDToken | undefined => + { + return generator.next().value; + } + const data = this.parseValueToken(getToken); + do + { + const t = getToken(); + if (t === undefined) + { + return data; + } + if (t.type !== LLSDTokenType.WHITESPACE) + { + throw new Error('Unexpected token at end of document: ' + t.value); + } + } + while (true); + } + + /* + private static readObject(cont: LLSDParserContainer): LLSDType + { + let tokenType: LLSDTokenType | null = null; + const stack: LLSDObject[] = []; + while (cont.pos < cont.str.length) + { + const subString = cont.str.slice(cont.pos); + let value: string | null = null; + for (const { regex, type } of this.tokenSpecs) + { + const tokenMatch = subString.match(regex); + if (tokenMatch) + { + value = tokenMatch[0]; + tokenType = type; + if (tokenMatch.length > 1) + { + value = tokenMatch[tokenMatch.length - 1]; + } + cont.pos += tokenMatch[0].length; // Move past this token + break; + } + } + if (tokenType === null) + { + tokenType = LLSDTokenType.UNKNOWN; + value = cont.str[cont.pos++]; + } + if (stack.length > 0) + { + if (stack[stack.length - 1].acceptToken(tokenType, value)) + { + // stack object completed + } + } + switch (tokenType) + { + case LLSDTokenType.WHITESPACE: + { + continue; + } + case LLSDTokenType.UNKNOWN: + { + throw new Error('Unexpected token: ' + value); + } + case LLSDTokenType.NULL: + { + return null; + } + case LLSDTokenType.MAP_START: + { + + break; + } + case LLSDTokenType.MAP_END: + break; + case LLSDTokenType.COLON: + break; + case LLSDTokenType.COMMA: + break; + case LLSDTokenType.ARRAY_START: + break; + case LLSDTokenType.ARRAY_END: + break; + case LLSDTokenType.BOOLEAN: + break; + case LLSDTokenType.INTEGER: + break; + case LLSDTokenType.REAL: + break; + case LLSDTokenType.UUID: + break; + case LLSDTokenType.STRING_FIXED: + break; + case LLSDTokenType.STRING_DYNAMIC_START: + break; + case LLSDTokenType.URI: + break; + case LLSDTokenType.DATE: + break; + case LLSDTokenType.BINARY_STATIC: + break; + case LLSDTokenType.BINARY_DYNAMIC_START: + break; + + } + } + } + + public static parse(input: string): LLSDType + { + const cont: LLSDParserContainer = { + str: input, + pos: 0 + }; + + const token = this.readObject(cont); + if (cont.pos < input.length) + { + throw new Error('Only one root object expected'); + } + return token; + } + */ +} diff --git a/lib/classes/python/PythonObject.ts b/lib/classes/llsd/LLSDObject.ts similarity index 79% rename from lib/classes/python/PythonObject.ts rename to lib/classes/llsd/LLSDObject.ts index f897f52..1f37821 100644 --- a/lib/classes/python/PythonObject.ts +++ b/lib/classes/llsd/LLSDObject.ts @@ -1,4 +1,4 @@ -export abstract class PythonObject +export abstract class LLSDObject { public toString(): string { diff --git a/lib/classes/llsd/LLSDToken.ts b/lib/classes/llsd/LLSDToken.ts new file mode 100644 index 0000000..88ceb4e --- /dev/null +++ b/lib/classes/llsd/LLSDToken.ts @@ -0,0 +1,12 @@ +import { LLSDTokenType } from './LLSDTokenType'; + +export interface LLSDToken +{ + type: LLSDTokenType; + value: string; + rawValue: string; + dataContainer: { + input: string, + index: number + } +} diff --git a/lib/classes/llsd/LLSDTokenContainer.ts b/lib/classes/llsd/LLSDTokenContainer.ts new file mode 100644 index 0000000..22e547e --- /dev/null +++ b/lib/classes/llsd/LLSDTokenContainer.ts @@ -0,0 +1,9 @@ +import { LLSDToken } from './LLSDToken'; +import { LLSDTokenGenerator } from './LLSDTokenGenerator'; + +export interface LLSDTokenContainer +{ + tokens: LLSDToken[]; + index: number; + gen: LLSDTokenGenerator; +} diff --git a/lib/classes/llsd/LLSDTokenGenerator.ts b/lib/classes/llsd/LLSDTokenGenerator.ts new file mode 100644 index 0000000..721ac33 --- /dev/null +++ b/lib/classes/llsd/LLSDTokenGenerator.ts @@ -0,0 +1,3 @@ +import { LLSDToken } from './LLSDToken'; + +export type LLSDTokenGenerator = () => LLSDToken | undefined; diff --git a/lib/classes/llsd/LLSDTokenSpec.ts b/lib/classes/llsd/LLSDTokenSpec.ts new file mode 100644 index 0000000..492d3e2 --- /dev/null +++ b/lib/classes/llsd/LLSDTokenSpec.ts @@ -0,0 +1,7 @@ +import { LLSDTokenType } from './LLSDTokenType'; + +export interface LLSDTokenSpec +{ + regex: RegExp; + type: LLSDTokenType; +} diff --git a/lib/classes/llsd/LLSDTokenType.ts b/lib/classes/llsd/LLSDTokenType.ts new file mode 100644 index 0000000..20ef5a8 --- /dev/null +++ b/lib/classes/llsd/LLSDTokenType.ts @@ -0,0 +1,23 @@ +export enum LLSDTokenType +{ + UNKNOWN = 0, + WHITESPACE, + NULL, + MAP_START, + MAP_END, + COLON, + COMMA, + ARRAY_START, + ARRAY_END, + BOOLEAN, + INTEGER, + REAL, + UUID, + STRING_FIXED_SINGLE, + STRING_FIXED_DOUBLE, + STRING_DYNAMIC_START, + URI, + DATE, + BINARY_STATIC, + BINARY_DYNAMIC_START, +} diff --git a/lib/classes/llsd/LLSDType.ts b/lib/classes/llsd/LLSDType.ts new file mode 100644 index 0000000..09dafd8 --- /dev/null +++ b/lib/classes/llsd/LLSDType.ts @@ -0,0 +1,4 @@ +import { LLSDObject } from './LLSDObject'; +import { UUID } from '../UUID'; + +export type LLSDType = number | boolean | string | Buffer | LLSDObject | null | UUID | Date | LLSDType[]; diff --git a/lib/classes/python/PythonDict.ts b/lib/classes/python/PythonDict.ts deleted file mode 100644 index cf8652d..0000000 --- a/lib/classes/python/PythonDict.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { PythonTokenContainer } from './PythonTokenContainer'; -import { PythonObject } from './PythonObject'; -import { PythonTokenType } from './PythonTokenType'; -import { PythonType } from './PythonType'; -import { PythonParser } from './PythonParser'; -import { PythonTuple } from './PythonTuple'; - -// Define PythonKey type for dictionary keys -export type PythonKey = string | boolean | PythonType[] | number | PythonTuple; - -export class PythonDict extends PythonObject -{ - public data: Map = new Map(); - - public static parse(container: PythonTokenContainer): PythonDict - { - const dict = new PythonDict(); - - let isKey = true; - let key: PythonKey | null = null; - - while (container.index < container.tokens.length) - { - const token = container.tokens[container.index]; - - switch (token.type) - { - case PythonTokenType.BRACE_END: - { - if (isKey) - { - // The last token is a key, which is invalid - throw new Error('Unexpected end of dictionary: Expected a key-value pair.'); - } - - container.index++; - return dict; - } - case PythonTokenType.COLON: - { - if (!isKey) - { - throw new Error('Expected a key before the colon in a dictionary.'); - } - isKey = false; - container.index++; - break; - } - case PythonTokenType.COMMA: - { - if (isKey) - { - throw new Error('No value provided with dictionary key'); - } - isKey = true; - container.index++; - break; - } - default: - { - if (isKey) - { - // Parse the key and check its type - key = PythonParser.parseValueToken(container) as PythonKey; - - if ( - typeof key !== 'string' && - typeof key !== 'number' && - typeof key !== 'boolean' && - !(key instanceof PythonTuple) && // Check if it's a PythonTuple - typeof key !== 'object' // Allow floats - ) - { - throw new Error('Invalid key type in a dictionary.'); - } - } - else - { - // Parse the value - if (key === null) - { - throw new Error('Key cannot be null in a dictionary.'); - } - const value = PythonParser.parseValueToken(container); - dict.data.set(key, value); - key = null; - } - } - } - } - - throw new Error('Expected close brace } in dictionary'); - } - - get length(): number - { - return Object.keys(this.data).length; - } - - public get(key: PythonKey): PythonType | undefined - { - return this.data.get(key); - } - - public toJSON(): unknown - { - return Object.fromEntries(this.data); - } -} diff --git a/lib/classes/python/PythonList.ts b/lib/classes/python/PythonList.ts deleted file mode 100644 index c12b39a..0000000 --- a/lib/classes/python/PythonList.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { PythonTokenContainer } from './PythonTokenContainer'; -import { PythonTokenType } from './PythonTokenType'; -import { PythonObject } from './PythonObject'; -import { PythonType } from './PythonType'; -import { PythonParser } from './PythonParser'; - -export class PythonList extends PythonObject -{ - public data: PythonType[] = []; - - public static parse(container: PythonTokenContainer): PythonList - { - let expectingComma = false; - const list = new PythonList(); - do - { - const token = container.tokens[container.index]; - switch (token.type) - { - case PythonTokenType.LIST_END: - { - container.index++; - return list; - } - case PythonTokenType.COMMA: - { - if (!expectingComma) - { - throw new Error('Unexpected comma in list'); - } - expectingComma = false; - container.index++; - break; - } - default: - { - if (expectingComma) - { - throw new Error('Unexpected token') - } - list.data.push(PythonParser.parseValueToken(container)); - expectingComma = true; - } - } - } - while (container.index < container.tokens.length); - throw new Error('Expected ] end bracket in list') - } - - public get(index: number): PythonType | undefined - { - return this.data[index]; - } - - get length(): number - { - return this.data.length; - } - - public toString(): string - { - return '[' + this.data.join(', ') + ']'; - } - - public toJSON(): unknown - { - return this.data; - } -} diff --git a/lib/classes/python/PythonParser.spec.ts b/lib/classes/python/PythonParser.spec.ts deleted file mode 100644 index 23de412..0000000 --- a/lib/classes/python/PythonParser.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { PythonParser } from './PythonParser'; - -import * as assert from 'assert'; -import { PythonDict } from './PythonDict'; -import { PythonList } from './PythonList'; -import { PythonTuple } from './PythonTuple'; - -describe('PythonParser', () => -{ - describe('parse', () => - { - it('can parse a complex python dictionary notation', () => - { - const notationDoc = `{ - "nested_dict": { - "key1": "value1", - "key2": { - "inner_key": "inner_value" - } - }, - "list": [1, 2, 3, [4, 5]], - "boolean": True, - "tuple": (1, 2, ("nested_tuple", 3)), - "bytes": b'hello', - "float": 3.14, - 'integer': 42, - "hex_number": 0x1A, - "octal_number": 0o52, - "string_single": 'single-quoted\\' string', - "string_double": "double-quoted \\" string", - "string_triple_single": '''triple-quoted\' -single-quoted string''', - "string_triple_double": """triple-quoted\" -double-quoted string""", - "raw_string_single": r'raw single-quoted\\ string', - "raw_string_double": r"raw double-quoted\\ string", - "raw_string_triple_single": r'''raw triple\\''-quoted -single-quoted string''', - "raw_string_triple_double": r"""raw triple\\""-quoted -double-quoted string""" -}`; - const parsed = PythonParser.parse(notationDoc); - if (!(parsed instanceof PythonDict)) - { - assert(false); - return; - } - - const nested = parsed.get('nested_dict'); - assert.ok(nested); - if (!(nested instanceof PythonDict)) - { - assert(false); - } - else - { - assert.equal(nested.get('key1'), 'value1'); - const key2 = nested.get('key2'); - if (!(key2 instanceof PythonDict)) - { - assert(false); - } - else - { - assert.equal(key2.get('inner_key'), 'inner_value'); - } - } - - const list = parsed.get('list'); - if (!(list instanceof PythonList)) - { - assert(false); - } - else - { - assert.equal(list.length, 4); - assert.equal(list.get(0), 1); - assert.equal(list.get(1), 2); - assert.equal(list.get(2), 3); - const nestedList = list.get(3); - if (!(nestedList instanceof PythonList)) - { - assert(false); - } - else - { - assert.equal(nestedList.get(0), 4); - assert.equal(nestedList.get(1), 5); - } - assert.equal(list.get(4), undefined); - } - - assert.equal(parsed.get('boolean'), true); - const tuple = parsed.get('tuple'); - if (!(tuple instanceof PythonTuple)) - { - assert(false); - } - else - { - assert.equal(tuple.get(0), 1); - assert.equal(tuple.get(1), 2); - const nestedTuple = tuple.get(2); - if (!(nestedTuple instanceof PythonTuple)) - { - assert(false); - } - else - { - assert.equal(nestedTuple.get(0), 'nested_tuple'); - assert.equal(nestedTuple.get(1), 3); - } - assert.equal(tuple.get(3), undefined); - } - const buf = parsed.get('bytes'); - if (buf instanceof Buffer) - { - assert.equal(Buffer.from('hello', 'binary').compare(buf), 0); - } - else - { - assert(false); - } - assert.equal(parsed.get('float'), 3.14); - assert.equal(parsed.get('integer'), 42); - assert.equal(parsed.get('hex_number'), 26); - assert.equal(parsed.get('octal_number'), 42); - assert.equal(parsed.get('string_single'), 'single-quoted\\\' string'); - assert.equal(parsed.get('string_double'), 'double-quoted \\" string'); - assert.equal(parsed.get('string_triple_single'), 'triple-quoted\'\nsingle-quoted string'); - assert.equal(parsed.get('string_triple_double'), 'triple-quoted\"\ndouble-quoted string'); - - /* - raw_string_single": r'raw single-quoted\ string', - "raw_string_double": r"raw double-quoted\ string", - "raw_string_triple_single": r'''raw triple\''-quoted -single-quoted string''', - "raw_string_triple_double": r"""raw triple\''-quoted -double-quoted string""" - */ - assert.equal(parsed.get('raw_string_single'), 'raw single-quoted\\ string'); - assert.equal(parsed.get('raw_string_double'), 'raw double-quoted\\ string'); - assert.equal(parsed.get('raw_string_triple_single'), 'raw triple\\\'\'-quoted\nsingle-quoted string'); - assert.equal(parsed.get('raw_string_triple_double'), 'raw triple\\""-quoted\ndouble-quoted string'); - }); - }); -}); - diff --git a/lib/classes/python/PythonParser.ts b/lib/classes/python/PythonParser.ts deleted file mode 100644 index ae204d1..0000000 --- a/lib/classes/python/PythonParser.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { PythonTokenType } from './PythonTokenType'; -import { PythonToken } from './PythonToken'; -import { PythonSet } from './PythonSet'; -import { PythonTokenContainer } from './PythonTokenContainer'; -import { PythonType } from './PythonType'; -import { PythonList } from './PythonList'; -import { PythonTuple } from './PythonTuple'; - -interface TokenSpec -{ - regex: RegExp; - type: PythonTokenType; -} - -export class PythonParser -{ - private static tokenSpecs: TokenSpec[] = - [ - { regex: /^\s+/, type: PythonTokenType.UNKNOWN }, // WHITESPACE is treated as UNKNOWN - { regex: /^{/, type: PythonTokenType.BRACE_START }, - { regex: /^}/, type: PythonTokenType.BRACE_END }, - { regex: /^[:]/, type: PythonTokenType.COLON }, - { regex: /^[,]/, type: PythonTokenType.COMMA }, - { regex: /^None\b/, type: PythonTokenType.NONE }, - { regex: /^(True|False)\b/, type: PythonTokenType.BOOLEAN }, - { regex: /^((?:-?[0-9]+\.[0-9]*)|(?:-?[0.9]*\.[0-9]+))/, type: PythonTokenType.FLOAT }, - { regex: /^\d+\b/, type: PythonTokenType.INTEGER }, - { regex: /^0x([0-9a-fA-F]+\b)/, type: PythonTokenType.HEX }, - { regex: /^0o([0-7]+)/, type: PythonTokenType.OCTAL }, - { regex: /^\(/, type: PythonTokenType.TUPLE_START }, - { regex: /^\)/, type: PythonTokenType.TUPLE_END }, - { regex: /^\[/, type: PythonTokenType.LIST_START }, - { regex: /^\]/, type: PythonTokenType.LIST_END }, - { regex: /^"""((?:[^"]*|\n|\\"|")*?)"""/, type: PythonTokenType.STRING }, // triple double quoted string - { regex: /^'''((?:[^']*|\n|\\'|')*?)'''/, type: PythonTokenType.STRING }, // triple single quoted string - { regex: /^'([^'\\]*(?:\\.[^'\\\n]*)*)'/, type: PythonTokenType.STRING }, // single quoted string - { regex: /^"([^"\\]*(?:\\.[^"\\\n]*)*)"/, type: PythonTokenType.STRING }, // double quoted string - - { regex: /^b"""((?:[^"]*|\n|\\"|")*?)"""/, type: PythonTokenType.BINARY_STRING }, // triple double quoted string - { regex: /^b'''((?:[^']*|\n|\\'|')*?)'''/, type: PythonTokenType.BINARY_STRING }, // triple single quoted string - { regex: /^b'([^'\\]*(?:\\.[^'\\\n]*)*)'/, type: PythonTokenType.BINARY_STRING }, // single quoted string - { regex: /^b"([^"\\]*(?:\\.[^"\\\n]*)*)"/, type: PythonTokenType.BINARY_STRING }, // double quoted string - - { regex: /^r"""((?:[^"]*|\n|")*?)"""/, type: PythonTokenType.RAW_STRING }, // triple double quoted string - { regex: /^r'''((?:[^']*|\n|')*?)'''/, type: PythonTokenType.RAW_STRING }, // triple single quoted string - { regex: /^r'([^'\n]*?)'/, type: PythonTokenType.RAW_STRING }, // single quoted string - { regex: /^r"([^"\n]*?)"/, type: PythonTokenType.RAW_STRING }, // double quoted string - - { regex: /^-?\d+\.?\d*[eE][-+]?\d+/, type: PythonTokenType.FLOAT }, // Scientific notation - { regex: /^\d+(_\d+)*\b/, type: PythonTokenType.INTEGER }, // Integer with underscores, e.g., 1_000_000 - { regex: /^[^\s:{},"'\[\]\(\)]+/, type: PythonTokenType.UNKNOWN } // Catch all for other non-structured sequences - ]; - - private static* tokenize(input: string): Generator - { - let index = 0; - while (index < input.length) - { - const currentInput = input.slice(index); - - if (currentInput.length === 0) - { - return; // End of input - } - - let matched = false; - for (const { regex, type } of PythonParser.tokenSpecs) - { - const tokenMatch = currentInput.match(regex); - if (tokenMatch) - { - matched = true; - let value = tokenMatch[0]; - if (tokenMatch.length > 1) - { - value = tokenMatch[tokenMatch.length - 1]; - } - index += tokenMatch[0].length; // Move past this token - - if (type !== PythonTokenType.UNKNOWN) // WHITESPACE is UNKNOWN and not yielded - { - yield { type, value }; - } - break; - } - } - - if (!matched) - { - throw new Error(`Unexpected token at index ${index}: "${currentInput[0]}"`); - } - } - } - - public static parseValueToken(container: PythonTokenContainer): PythonType - { - const t = container.tokens[container.index++]; - switch (t.type) - { - case PythonTokenType.BRACE_START: - { - return PythonSet.parse(container); - } - case PythonTokenType.STRING: - { - return t.value; - } - case PythonTokenType.BINARY_STRING: - { - return Buffer.from(t.value, 'binary'); - } - case PythonTokenType.RAW_STRING: - { - return t.value; - } - case PythonTokenType.BOOLEAN: - { - return t.value === 'True'; - } - case PythonTokenType.LIST_START: - { - return PythonList.parse(container); - } - case PythonTokenType.TUPLE_START: - { - return PythonTuple.parse(container); - } - case PythonTokenType.NONE: - { - return null; - } - case PythonTokenType.HEX: - { - return parseInt(t.value, 16); - } - case PythonTokenType.OCTAL: - { - return parseInt(t.value, 8); - } - case PythonTokenType.INTEGER: - { - return parseInt(t.value, 10); - } - case PythonTokenType.FLOAT: - { - return parseFloat(t.value); - } - default: - throw new Error('Unexpected token: ' + PythonTokenType[t.type]); - } - } - - public static parse(input: string): PythonType - { - const cont = new PythonTokenContainer() - for (const token of PythonParser.tokenize(input)) - { - cont.tokens.push(token); - } - - const parsedToken = this.parseValueToken(cont); - if (cont.index < cont.tokens.length) - { - throw new Error('Only one token expected at root level'); - } - return parsedToken; - } -} diff --git a/lib/classes/python/PythonSet.ts b/lib/classes/python/PythonSet.ts deleted file mode 100644 index accdb79..0000000 --- a/lib/classes/python/PythonSet.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { PythonTokenContainer } from './PythonTokenContainer'; -import { PythonTokenType } from './PythonTokenType'; -import { PythonDict } from './PythonDict'; -import { PythonObject } from './PythonObject'; -import { PythonType } from './PythonType'; -import { PythonParser } from './PythonParser'; - -export class PythonSet extends PythonObject -{ - public data = new Set(); - - public static parse(container: PythonTokenContainer): PythonSet | PythonDict - { - let expectingComma = false; - const startIndex = container.index; - const set = new PythonSet(); - do - { - const token = container.tokens[container.index]; - switch (token.type) - { - case PythonTokenType.BRACE_END: - { - if (container.index === startIndex) - { - // Empty braces, this is an empty PythonDict - return new PythonDict(); - } - else - { - container.index++; - return set; - } - } - case PythonTokenType.COMMA: - { - if (!expectingComma) - { - throw new Error('Unexpected comma in list'); - } - expectingComma = false; - container.index++; - break; - } - case PythonTokenType.COLON: - { - // This is a dictionary, not a set, start again.. - container.index = startIndex; - return PythonDict.parse(container); - } - default: - { - if (expectingComma) - { - throw new Error('Unexpected token') - } - set.data.add(PythonParser.parseValueToken(container)); - expectingComma = true; - } - } - } - while (container.index < container.tokens.length); - throw new Error('Expected } end brace in set') - } - - public toJSON(): unknown - { - return Array.from(this.data); - } -} diff --git a/lib/classes/python/PythonToken.ts b/lib/classes/python/PythonToken.ts deleted file mode 100644 index 73177dc..0000000 --- a/lib/classes/python/PythonToken.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PythonTokenType } from './PythonTokenType'; - -export interface PythonToken -{ - type: PythonTokenType; - value: string; -} diff --git a/lib/classes/python/PythonTokenContainer.ts b/lib/classes/python/PythonTokenContainer.ts deleted file mode 100644 index 1dcc01c..0000000 --- a/lib/classes/python/PythonTokenContainer.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PythonToken } from './PythonToken'; - -export class PythonTokenContainer -{ - tokens: PythonToken[] = []; - index = 0; -} diff --git a/lib/classes/python/PythonTokenType.ts b/lib/classes/python/PythonTokenType.ts deleted file mode 100644 index d39a945..0000000 --- a/lib/classes/python/PythonTokenType.ts +++ /dev/null @@ -1,21 +0,0 @@ -export enum PythonTokenType -{ - NONE, - BRACE_START, - BRACE_END, - COLON, - COMMA, - BOOLEAN, - INTEGER, - FLOAT, - STRING, - BINARY_STRING, - RAW_STRING, - LIST_START, - LIST_END, - TUPLE_START, - TUPLE_END, - HEX, - OCTAL, - UNKNOWN // Catch all for other sequences -} diff --git a/lib/classes/python/PythonTuple.ts b/lib/classes/python/PythonTuple.ts deleted file mode 100644 index 0904802..0000000 --- a/lib/classes/python/PythonTuple.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { PythonTokenContainer } from './PythonTokenContainer'; -import { PythonTokenType } from './PythonTokenType'; -import { PythonObject } from './PythonObject'; -import { PythonType } from './PythonType'; -import { PythonParser } from './PythonParser'; - -export class PythonTuple extends PythonObject -{ - public data: PythonType[] = []; - - public static parse(container: PythonTokenContainer): PythonTuple - { - let expectingComma = false; - const tuple = new PythonTuple(); - do - { - const token = container.tokens[container.index]; - switch (token.type) - { - case PythonTokenType.TUPLE_END: - { - container.index++; - return tuple; - } - case PythonTokenType.COMMA: - { - if (!expectingComma) - { - throw new Error('Unexpected comma in list'); - } - expectingComma = false; - container.index++; - break; - } - default: - { - if (expectingComma) - { - throw new Error('Unexpected token') - } - tuple.data.push(PythonParser.parseValueToken(container)); - expectingComma = true; - break; - } - } - } - while (container.index < container.tokens.length); - throw new Error('Expected ) end bracket in tuple') - } - - public get(index: number): PythonType | undefined - { - return this.data[index]; - } - - get length(): number - { - return this.data.length; - } - - public toString(): string - { - return '(' + this.data.join(', ') + ')'; - } - - public toJSON(): unknown - { - return this.data; - } -} diff --git a/lib/classes/python/PythonType.ts b/lib/classes/python/PythonType.ts deleted file mode 100644 index 38693f2..0000000 --- a/lib/classes/python/PythonType.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PythonObject } from './PythonObject'; - -export type PythonType = number | boolean | string | Buffer | PythonObject | null; diff --git a/package.json b/package.json index 8f30bbb..a925993 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.6.4", + "version": "0.6.5", "description": "A node.js interface for Second Life.", "main": "dist/lib/index.js", "types": "dist/lib/index.d.ts",