You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					199 lines
				
				5.9 KiB
			
		
		
			
		
	
	
					199 lines
				
				5.9 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								const {Parser: AcornParser, isNewLine: acornIsNewLine, getLineInfo: acornGetLineInfo} = require('acorn');
							 | 
						||
| 
								 | 
							
								const {full: acornWalkFull} = require('acorn-walk');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const INTERNAL_STATE_NAME = 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function assertType(node, type) {
							 | 
						||
| 
								 | 
							
									if (!node) throw new Error(`None existent node expected '${type}'`);
							 | 
						||
| 
								 | 
							
									if (node.type !== type) throw new Error(`Invalid node type '${node.type}' expected '${type}'`);
							 | 
						||
| 
								 | 
							
									return node;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function makeNiceSyntaxError(message, code, filename, location, tokenizer) {
							 | 
						||
| 
								 | 
							
									const loc = acornGetLineInfo(code, location);
							 | 
						||
| 
								 | 
							
									let end = location;
							 | 
						||
| 
								 | 
							
									while (end < code.length && !acornIsNewLine(code.charCodeAt(end))) {
							 | 
						||
| 
								 | 
							
										end++;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									let markerEnd = tokenizer.start === location ? tokenizer.end : location + 1;
							 | 
						||
| 
								 | 
							
									if (!markerEnd || markerEnd > end) markerEnd = end;
							 | 
						||
| 
								 | 
							
									let markerLen = markerEnd - location;
							 | 
						||
| 
								 | 
							
									if (markerLen <= 0) markerLen = 1;
							 | 
						||
| 
								 | 
							
									if (message === 'Unexpected token') {
							 | 
						||
| 
								 | 
							
										const type = tokenizer.type;
							 | 
						||
| 
								 | 
							
										if (type.label === 'name' || type.label === 'privateId') {
							 | 
						||
| 
								 | 
							
											message = 'Unexpected identifier';
							 | 
						||
| 
								 | 
							
										} else if (type.label === 'eof') {
							 | 
						||
| 
								 | 
							
											message = 'Unexpected end of input';
							 | 
						||
| 
								 | 
							
										} else if (type.label === 'num') {
							 | 
						||
| 
								 | 
							
											message = 'Unexpected number';
							 | 
						||
| 
								 | 
							
										} else if (type.label === 'string') {
							 | 
						||
| 
								 | 
							
											message = 'Unexpected string';
							 | 
						||
| 
								 | 
							
										} else if (type.label === 'regexp') {
							 | 
						||
| 
								 | 
							
											message = 'Unexpected token \'/\'';
							 | 
						||
| 
								 | 
							
											markerLen = 1;
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											const token = tokenizer.value || type.label;
							 | 
						||
| 
								 | 
							
											message = `Unexpected token '${token}'`;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									const error = new SyntaxError(message);
							 | 
						||
| 
								 | 
							
									if (!filename) return error;
							 | 
						||
| 
								 | 
							
									const line = code.slice(location - loc.column, end);
							 | 
						||
| 
								 | 
							
									const marker = line.slice(0, loc.column).replace(/\S/g, ' ') + '^'.repeat(markerLen);
							 | 
						||
| 
								 | 
							
									error.stack = `${filename}:${loc.line}\n${line}\n${marker}\n\n${error.stack}`;
							 | 
						||
| 
								 | 
							
									return error;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function transformer(args, body, isAsync, isGenerator, filename) {
							 | 
						||
| 
								 | 
							
									let code;
							 | 
						||
| 
								 | 
							
									let argsOffset;
							 | 
						||
| 
								 | 
							
									if (args === null) {
							 | 
						||
| 
								 | 
							
										code = body;
							 | 
						||
| 
								 | 
							
										// Note: Keywords are not allows to contain u escapes
							 | 
						||
| 
								 | 
							
										if (!/\b(?:catch|import|async)\b/.test(code)) {
							 | 
						||
| 
								 | 
							
											return {__proto__: null, code, hasAsync: false};
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										code = isAsync ? '(async function' : '(function';
							 | 
						||
| 
								 | 
							
										if (isGenerator) code += '*';
							 | 
						||
| 
								 | 
							
										code += ' anonymous(';
							 | 
						||
| 
								 | 
							
										code += args;
							 | 
						||
| 
								 | 
							
										argsOffset = code.length;
							 | 
						||
| 
								 | 
							
										code += '\n) {\n';
							 | 
						||
| 
								 | 
							
										code += body;
							 | 
						||
| 
								 | 
							
										code += '\n})';
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									const parser = new AcornParser({
							 | 
						||
| 
								 | 
							
										__proto__: null,
							 | 
						||
| 
								 | 
							
										ecmaVersion: 2022,
							 | 
						||
| 
								 | 
							
										allowAwaitOutsideFunction: args === null && isAsync,
							 | 
						||
| 
								 | 
							
										allowReturnOutsideFunction: args === null
							 | 
						||
| 
								 | 
							
									}, code);
							 | 
						||
| 
								 | 
							
									let ast;
							 | 
						||
| 
								 | 
							
									try {
							 | 
						||
| 
								 | 
							
										ast = parser.parse();
							 | 
						||
| 
								 | 
							
									} catch (e) {
							 | 
						||
| 
								 | 
							
										// Try to generate a nicer error message.
							 | 
						||
| 
								 | 
							
										if (e instanceof SyntaxError && e.pos !== undefined) {
							 | 
						||
| 
								 | 
							
											let message = e.message;
							 | 
						||
| 
								 | 
							
											const match = message.match(/^(.*) \(\d+:\d+\)$/);
							 | 
						||
| 
								 | 
							
											if (match) message = match[1];
							 | 
						||
| 
								 | 
							
											e = makeNiceSyntaxError(message, code, filename, e.pos, parser);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										throw e;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (args !== null) {
							 | 
						||
| 
								 | 
							
										const pBody = assertType(ast, 'Program').body;
							 | 
						||
| 
								 | 
							
										if (pBody.length !== 1) throw new SyntaxError('Single function literal required');
							 | 
						||
| 
								 | 
							
										const expr = pBody[0];
							 | 
						||
| 
								 | 
							
										if (expr.type !== 'ExpressionStatement') throw new SyntaxError('Single function literal required');
							 | 
						||
| 
								 | 
							
										const func = expr.expression;
							 | 
						||
| 
								 | 
							
										if (func.type !== 'FunctionExpression') throw new SyntaxError('Single function literal required');
							 | 
						||
| 
								 | 
							
										if (func.body.start !== argsOffset + 3) throw new SyntaxError('Unexpected end of arg string');
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									const insertions = [];
							 | 
						||
| 
								 | 
							
									let hasAsync = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									const TO_LEFT = -100;
							 | 
						||
| 
								 | 
							
									const TO_RIGHT = 100;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									let internStateValiable = undefined;
							 | 
						||
| 
								 | 
							
									let tmpname = 'VM2_INTERNAL_TMPNAME';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									acornWalkFull(ast, (node, state, type) => {
							 | 
						||
| 
								 | 
							
										if (type === 'Function') {
							 | 
						||
| 
								 | 
							
											if (node.async) hasAsync = true;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										const nodeType = node.type;
							 | 
						||
| 
								 | 
							
										if (nodeType === 'CatchClause') {
							 | 
						||
| 
								 | 
							
											const param = node.param;
							 | 
						||
| 
								 | 
							
											if (param) {
							 | 
						||
| 
								 | 
							
												if (param.type === 'ObjectPattern') {
							 | 
						||
| 
								 | 
							
													insertions.push({
							 | 
						||
| 
								 | 
							
														__proto__: null,
							 | 
						||
| 
								 | 
							
														pos: node.start,
							 | 
						||
| 
								 | 
							
														order: TO_RIGHT,
							 | 
						||
| 
								 | 
							
														code: `catch($tmpname){try{throw ${INTERNAL_STATE_NAME}.handleException($tmpname);}`
							 | 
						||
| 
								 | 
							
													});
							 | 
						||
| 
								 | 
							
													insertions.push({
							 | 
						||
| 
								 | 
							
														__proto__: null,
							 | 
						||
| 
								 | 
							
														pos: node.body.end,
							 | 
						||
| 
								 | 
							
														order: TO_LEFT,
							 | 
						||
| 
								 | 
							
														code: `}`
							 | 
						||
| 
								 | 
							
													});
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													const name = assertType(param, 'Identifier').name;
							 | 
						||
| 
								 | 
							
													const cBody = assertType(node.body, 'BlockStatement');
							 | 
						||
| 
								 | 
							
													if (cBody.body.length > 0) {
							 | 
						||
| 
								 | 
							
														insertions.push({
							 | 
						||
| 
								 | 
							
															__proto__: null,
							 | 
						||
| 
								 | 
							
															pos: cBody.body[0].start,
							 | 
						||
| 
								 | 
							
															order: TO_LEFT,
							 | 
						||
| 
								 | 
							
															code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});`
							 | 
						||
| 
								 | 
							
														});
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										} else if (nodeType === 'WithStatement') {
							 | 
						||
| 
								 | 
							
											insertions.push({
							 | 
						||
| 
								 | 
							
												__proto__: null,
							 | 
						||
| 
								 | 
							
												pos: node.object.start,
							 | 
						||
| 
								 | 
							
												order: TO_LEFT,
							 | 
						||
| 
								 | 
							
												code: INTERNAL_STATE_NAME + '.wrapWith('
							 | 
						||
| 
								 | 
							
											});
							 | 
						||
| 
								 | 
							
											insertions.push({
							 | 
						||
| 
								 | 
							
												__proto__: null,
							 | 
						||
| 
								 | 
							
												pos: node.object.end,
							 | 
						||
| 
								 | 
							
												order: TO_RIGHT,
							 | 
						||
| 
								 | 
							
												code: ')'
							 | 
						||
| 
								 | 
							
											});
							 | 
						||
| 
								 | 
							
										} else if (nodeType === 'Identifier') {
							 | 
						||
| 
								 | 
							
											if (node.name === INTERNAL_STATE_NAME) {
							 | 
						||
| 
								 | 
							
												if (internStateValiable === undefined || internStateValiable.start > node.start) {
							 | 
						||
| 
								 | 
							
													internStateValiable = node;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											} else if (node.name.startsWith(tmpname)) {
							 | 
						||
| 
								 | 
							
												tmpname = node.name + '_UNIQUE';
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										} else if (nodeType === 'ImportExpression') {
							 | 
						||
| 
								 | 
							
											insertions.push({
							 | 
						||
| 
								 | 
							
												__proto__: null,
							 | 
						||
| 
								 | 
							
												pos: node.start,
							 | 
						||
| 
								 | 
							
												order: TO_RIGHT,
							 | 
						||
| 
								 | 
							
												code: INTERNAL_STATE_NAME + '.'
							 | 
						||
| 
								 | 
							
											});
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (internStateValiable) {
							 | 
						||
| 
								 | 
							
										throw makeNiceSyntaxError('Use of internal vm2 state variable', code, filename, internStateValiable.start, {
							 | 
						||
| 
								 | 
							
											__proto__: null,
							 | 
						||
| 
								 | 
							
											start: internStateValiable.start,
							 | 
						||
| 
								 | 
							
											end: internStateValiable.end
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (insertions.length === 0) return {__proto__: null, code, hasAsync};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									insertions.sort((a, b) => (a.pos == b.pos ? a.order - b.order : a.pos - b.pos));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									let ncode = '';
							 | 
						||
| 
								 | 
							
									let curr = 0;
							 | 
						||
| 
								 | 
							
									for (let i = 0; i < insertions.length; i++) {
							 | 
						||
| 
								 | 
							
										const change = insertions[i];
							 | 
						||
| 
								 | 
							
										ncode += code.substring(curr, change.pos) + change.code.replace(/\$tmpname/g, tmpname);
							 | 
						||
| 
								 | 
							
										curr = change.pos;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									ncode += code.substring(curr);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return {__proto__: null, code: ncode, hasAsync};
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.INTERNAL_STATE_NAME = INTERNAL_STATE_NAME;
							 | 
						||
| 
								 | 
							
								exports.transformer = transformer;
							 |