Dirty hack to fix projects that use literal Infinity

https://scratch.mit.edu/projects/10590338/
This commit is contained in:
Thomas Weber committed 2020-11-17 14:29:31 -06:00
1 parent d3e4e144d7
commit 0d6a033c6d
2 files changed
+275 -1

No files matched your search

+260
View File
@@ -0,0 +1,260 @@
// Imported from https://github.com/forkphorus/forkphorus/blob/master/src/json.ts
// Intended as a temporary fix for https://github.com/LLK/scratch-parser/issues/60
/* eslint-disable no-constant-condition */
class JSONParser {
constructor (source) {
this.source = source;
this.index = 0;
}
parse () {
return this.parseValue();
}
lineInfo () {
let line = 0;
let column = 0;
for (let i = 0; i < this.index; i++) {
if (this.source[i] === '\n') {
line++;
column = 0;
} else {
column++;
}
}
return {line: line + 1, column: column + 1};
}
error (message) {
const {line, column} = this.lineInfo();
throw new SyntaxError(`JSONParser: ${message} (Line ${line} Column ${column})`);
}
char () {
return this.charAt(this.index);
}
charAt (index) {
if (index >= this.source.length) {
this.error('Unexpected end of input');
}
return this.source[index];
}
next () {
this.index++;
}
expect (char) {
if (this.char() !== char) {
this.error(`Expected '${char}' but found '${this.char()}'`);
}
this.next();
}
peek (length = 1, offset = 1) {
if (length === 1) {
return this.charAt(this.index + offset);
}
let result = '';
for (let i = 0; i < length; i++) {
result += this.charAt(this.index + offset + i);
}
return result;
}
skipWhitespace () {
while (/\s/.test(this.char())) {
this.next();
}
}
parseValue () {
this.skipWhitespace();
const char = this.char();
switch (char) {
case '"': return this.parseString();
case '{': return this.parseObject();
case '[': return this.parseList();
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return this.parseNumber();
default: return this.parseWord();
}
}
parseWord () {
if (this.peek(4, 0) === 'null') {
for (let i = 0; i < 4; i++) {
this.next();
}
return null;
}
if (this.peek(4, 0) === 'true') {
for (let i = 0; i < 4; i++) {
this.next();
}
return true;
}
if (this.peek(5, 0) === 'false') {
for (let i = 0; i < 5; i++) {
this.next();
}
return false;
}
// Non-standard extensions
if (this.peek(8, 0) === 'Infinity') {
for (let i = 0; i < 8; i++) {
this.next();
}
return Infinity;
}
if (this.peek(9, 0) === '-Infinity') {
for (let i = 0; i < 9; i++) {
this.next();
}
return -Infinity;
}
if (this.peek(3, 0) === 'NaN') {
for (let i = 0; i < 3; i++) {
this.next();
}
return NaN;
}
this.error(`Unknown word (starts with ${this.char()})`);
}
parseNumber () {
let number = '';
while (true) {
number += this.char();
if (/[\d\.e+-]/i.test(this.peek())) {
this.next();
} else {
break;
}
}
this.next();
const value = +number;
if (Number.isNaN(value)) {
this.error(`Not a number: ${number}`);
}
return value;
}
parseString () {
this.expect('"');
let result = '';
if (this.char() === '"') {
this.next();
return '';
}
while (true) {
const char = this.char();
if (char === '\\') {
this.next();
switch (this.char()) {
case '"':
result += '"';
break;
case '/':
result += '/';
break;
case '\\':
result += '\\';
break;
case 'b':
result += '\b';
break;
case 'f':
result += '\f';
break;
case 'n':
result += '\n';
break;
case 'r':
result += '\r';
break;
case 't':
result += '\t';
break;
case 'u': {
let hexString = '';
for (let i = 0; i < 4; i++) {
this.next();
const char = this.char();
if (!/[0-9a-f]/i.test(char)) {
this.error(`Invalid hex code: ${char}`);
}
hexString += char;
}
const hexNumber = Number.parseInt(hexString, 16);
const letter = String.fromCharCode(hexNumber);
result += letter;
break;
}
default: this.error(`Invalid escape code: \\${this.char()}`);
}
} else {
result += char;
}
if (this.peek() === '"') {
break;
}
this.next();
}
this.next();
this.expect('"');
return result;
}
parseList () {
this.expect('[');
this.skipWhitespace();
if (this.char() === ']') {
this.next();
return [];
}
const result = [];
while (true) {
this.skipWhitespace();
const value = this.parseValue();
result.push(value);
this.skipWhitespace();
if (this.char() === ']') {
break;
}
this.expect(',');
}
this.expect(']');
return result;
}
parseObject () {
this.expect('{');
this.skipWhitespace();
if (this.char() === '}') {
this.next();
return {};
}
const result = Object.create(null);
while (true) {
this.skipWhitespace();
const key = this.parseString();
this.skipWhitespace();
this.expect(':');
const value = this.parseValue();
result[key] = value;
this.skipWhitespace();
if (this.char() === '}') {
break;
}
this.expect(',');
}
this.expect('}');
return result;
}
}
const parse = source => {
const parser = new JSONParser(source);
return parser.parse();
};
module.exports = parse;
+15 -1
View File
@@ -346,7 +346,21 @@ class VirtualMachine extends EventEmitter {
// The second argument of false below indicates to the validator that the
// input should be parsed/validated as an entire project (and not a single sprite)
validate(input, false, (error, res) => {
if (error) return reject(error);
if (error) {
// tw: if parsing failed, but the result appears to be JSON,
// try an alternative JSON parser that supports some non-standard literals.
// This is a dirty hack to fix https://github.com/LLK/scratch-parser/issues/60
if (input[0] !== '{' && input[0] !== '{'.charCodeAt(0)) {
return reject(error);
}
if (typeof input !== 'string') input = new TextDecoder().decode(input);
input = require('./tw-loose-json')(input);
input = JSON.stringify(input);
return validate(input, false, (error2, res2) => {
if (error2) return reject(error);
resolve(res2);
});
}
resolve(res);
});
})