Files
node-metaverse/lib/classes/llsd/LLSDNotationParser.ts
2023-11-10 23:57:40 +00:00

202 lines
7.7 KiB
TypeScript

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<LLSDToken | void>
{
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;
}
return this.parseValueToken(getToken);
}
}