Actually, the message format is LLSD notation not python (d'oh)
This commit is contained in:
320
lib/classes/llsd/LLSDNotationParser.ts
Normal file
320
lib/classes/llsd/LLSDNotationParser.ts
Normal file
@@ -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<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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user