mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-28 17:20:22 +00:00
added .mjs extension to JavaScript (#3783)
* added .mjs extension to JavaScript * add missing newline at end of file * add example from https://github.com/bmeck/composable-ast-walker/blob/master/example/constant_fold.mjs
This commit is contained in:
committed by
Colin Seymour
parent
6b06e47c67
commit
38bc5fd336
@@ -2078,6 +2078,7 @@ JavaScript:
|
||||
- ".jsfl"
|
||||
- ".jsm"
|
||||
- ".jss"
|
||||
- ".mjs"
|
||||
- ".njs"
|
||||
- ".pac"
|
||||
- ".sjs"
|
||||
|
||||
955
samples/JavaScript/constant_fold.mjs
Normal file
955
samples/JavaScript/constant_fold.mjs
Normal file
@@ -0,0 +1,955 @@
|
||||
// consumes <stdin> and performs constant folding
|
||||
// echo '"use strict";"_"[0],1+2;' | node constant_fold.js
|
||||
import _NodePath from '../NodePath';
|
||||
const {NodePath} = _NodePath;
|
||||
import _WalkCombinator from '../WalkCombinator';
|
||||
const {WalkCombinator} = _WalkCombinator;
|
||||
|
||||
const $CONSTEXPR = Symbol.for('$CONSTEXTR');
|
||||
const $CONSTVALUE = Symbol.for('$CONSTVALUE');
|
||||
const IS_EMPTY = path => {
|
||||
return (path.node.type === 'BlockStatement' && path.node.body.length === 0) ||
|
||||
path.node.type === 'EmptyStatement';
|
||||
};
|
||||
const IN_PRAGMA_POS = path => {
|
||||
if (path.parent && Array.isArray(path.parent.node)) {
|
||||
const siblings = path.parent.node;
|
||||
for (let i = 0; i < path.key; i++) {
|
||||
// preceded by non-pragma
|
||||
if (
|
||||
siblings[i].type !== 'ExpressionStatement' ||
|
||||
!IS_CONSTEXPR(siblings[i].expression) ||
|
||||
typeof CONSTVALUE(siblings[i].expression) !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const IS_PRAGMA = path => {
|
||||
if (path.parent && Array.isArray(path.parent.node)) {
|
||||
const siblings = path.parent.node;
|
||||
for (let i = 0; i < path.key + 1; i++) {
|
||||
// preceded by non-pragma
|
||||
if (
|
||||
siblings[i].type !== 'ExpressionStatement' ||
|
||||
!IS_CONSTEXPR(siblings[i].expression) ||
|
||||
typeof CONSTVALUE(siblings[i].expression) !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// worst case is the completion value
|
||||
const IS_NOT_COMPLETION = path => {
|
||||
while (true) {
|
||||
if (!path.parent) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
Array.isArray(path.parent.node) &&
|
||||
path.key !== path.parent.node.length - 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
path = path.parent;
|
||||
while (Array.isArray(path.node)) {
|
||||
path = path.parent;
|
||||
}
|
||||
if (/Function/.test(path.node.type)) {
|
||||
return true;
|
||||
} else if (path.node.type === 'Program') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
const REMOVE_IF_EMPTY = path => {
|
||||
if (IS_EMPTY(path)) REMOVE(path);
|
||||
return null;
|
||||
};
|
||||
const REPLACE_IF_EMPTY = (path, folded) => {
|
||||
if (IS_EMPTY(path)) return REPLACE(path, folded);
|
||||
return path;
|
||||
};
|
||||
const REMOVE = path => {
|
||||
if (Array.isArray(path.parent.node)) {
|
||||
path.parent.node.splice(path.key, 1);
|
||||
} else {
|
||||
path.parent.node[path.key] = null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const REPLACE = (path, folded) => {
|
||||
const replacement = new NodePath(path.parent, folded, path.key);
|
||||
path.parent.node[path.key] = folded;
|
||||
return replacement;
|
||||
};
|
||||
// no mutation, this is an atomic value
|
||||
const NEG_ZERO = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'UnaryExpression',
|
||||
operator: '-',
|
||||
argument: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 0,
|
||||
}),
|
||||
});
|
||||
const INFINITY = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'BinaryExpression',
|
||||
operator: '/',
|
||||
left: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 1,
|
||||
}),
|
||||
right: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 0,
|
||||
}),
|
||||
});
|
||||
const NEG_INFINITY = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'BinaryExpression',
|
||||
operator: '/',
|
||||
left: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 1,
|
||||
}),
|
||||
right: NEG_ZERO,
|
||||
});
|
||||
const EMPTY = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'EmptyStatement',
|
||||
});
|
||||
const NULL = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: null,
|
||||
});
|
||||
const NAN = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'BinaryExpression',
|
||||
operator: '/',
|
||||
left: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 0,
|
||||
}),
|
||||
right: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 0,
|
||||
}),
|
||||
});
|
||||
const UNDEFINED = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'UnaryExpression',
|
||||
operator: 'void',
|
||||
argument: Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'Literal',
|
||||
value: 0,
|
||||
}),
|
||||
});
|
||||
// ESTree doesn't like negative numeric literals
|
||||
// this also preserves -0
|
||||
const IS_UNARY_NEGATIVE = node => {
|
||||
if (
|
||||
node.type === 'UnaryExpression' &&
|
||||
node.operator === '-' &&
|
||||
typeof node.argument.value === 'number' &&
|
||||
node.argument.value === node.argument.value &&
|
||||
node.argument.type === 'Literal'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const IS_CONSTEXPR = node => {
|
||||
if (typeof node !== 'object' || node === null) {
|
||||
return false;
|
||||
}
|
||||
// DONT CALCULATE THINGS MULTIPLE TIMES!!@!@#
|
||||
if (node[$CONSTEXPR]) return true;
|
||||
if (node.type === 'ArrayExpression') {
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
const element = node.elements[i];
|
||||
// hole == null
|
||||
if (element !== null && !IS_CONSTEXPR(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (node.type === 'ObjectExpression') {
|
||||
for (let i = 0; i < node.properties.length; i++) {
|
||||
const element = node.properties[i];
|
||||
if (element.kind !== 'init') return false;
|
||||
if (element.method) return false;
|
||||
let key;
|
||||
if (element.computed) {
|
||||
// be sure {["y"]:1} works
|
||||
if (!IS_CONSTEXPR(element.key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!IS_CONSTEXPR(element.value)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (node.type === 'Literal' || IS_UNDEFINED(node) || IS_NAN(node)) {
|
||||
return true;
|
||||
}
|
||||
if (IS_UNARY_NEGATIVE(node)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const IS_NAN = node => {
|
||||
return node === NAN;
|
||||
};
|
||||
const IS_UNDEFINED = node => {
|
||||
return node === UNDEFINED;
|
||||
};
|
||||
const CONSTVALUE = node => {
|
||||
if (node[$CONSTVALUE]) {
|
||||
return node[$CONSTVALUE];
|
||||
}
|
||||
if (IS_UNDEFINED(node)) return void 0;
|
||||
if (IS_NAN(node)) return +'_';
|
||||
if (!IS_CONSTEXPR(node)) throw new Error('Not a CONSTEXPR');
|
||||
if (node.type === 'ArrayExpression') {
|
||||
let ret = [];
|
||||
ret.length = node.elements.length;
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
if (node.elements[i] !== null) {
|
||||
ret[i] = CONSTVALUE(node.elements[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (node.type === 'ObjectExpression') {
|
||||
let ret = Object.create(null);
|
||||
for (let i = 0; i < node.properties.length; i++) {
|
||||
const element = node.properties[i];
|
||||
let key;
|
||||
if (element.computed) {
|
||||
key = `${CONSTVALUE(element.key)}`;
|
||||
}
|
||||
else {
|
||||
key = element.key.name;
|
||||
}
|
||||
Object.defineProperty(ret, key, {
|
||||
// duplicate keys...
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: CONSTVALUE(element.value),
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
Object.freeze(ret);
|
||||
return ret;
|
||||
}
|
||||
if (IS_UNARY_NEGATIVE(node)) {
|
||||
return -node.argument.value;
|
||||
}
|
||||
if (node.regex !== void 0) {
|
||||
return new RegExp(node.regex.pattern, node.regex.flags);
|
||||
}
|
||||
return node.value;
|
||||
};
|
||||
const CONSTEXPRS = new Map();
|
||||
CONSTEXPRS.set(void 0, UNDEFINED);
|
||||
CONSTEXPRS.set(+'_', NAN);
|
||||
CONSTEXPRS.set(null, NULL);
|
||||
const TO_CONSTEXPR = value => {
|
||||
if (value === -Infinity) {
|
||||
return NEG_INFINITY;
|
||||
}
|
||||
if (value === Infinity) {
|
||||
return INFINITY;
|
||||
}
|
||||
let is_neg_zero = 1 / value === -Infinity;
|
||||
if (is_neg_zero) return NEG_ZERO;
|
||||
if (CONSTEXPRS.has(value)) {
|
||||
return CONSTEXPRS.get(value);
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
if (value < 0) {
|
||||
const CONSTEXPR = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
[$CONSTVALUE]: value,
|
||||
type: 'UnaryExpression',
|
||||
operator: '-',
|
||||
argument: Object.freeze({ type: 'Literal', value: -value }),
|
||||
});
|
||||
CONSTEXPRS.set(value, CONSTEXPR);
|
||||
return CONSTEXPR;
|
||||
}
|
||||
}
|
||||
if (
|
||||
value === null ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean' ||
|
||||
typeof value === 'string'
|
||||
) {
|
||||
const CONSTEXPR = Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
[$CONSTVALUE]: value,
|
||||
type: 'Literal',
|
||||
value,
|
||||
});
|
||||
CONSTEXPRS.set(value, CONSTEXPR);
|
||||
return CONSTEXPR;
|
||||
}
|
||||
// have to generate new one every time :-/
|
||||
if (Array.isArray(value)) {
|
||||
return Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'ArrayExpression',
|
||||
elements: Object.freeze(value.map(TO_CONSTEXPR)),
|
||||
});
|
||||
}
|
||||
if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.getPrototypeOf({}) && [...Object.getOwnPropertySymbols(value)].length === 0) {
|
||||
return Object.freeze({
|
||||
[$CONSTEXPR]: true,
|
||||
type: 'ObjectExpression',
|
||||
properties: Object.freeze(
|
||||
[...Object.getOwnPropertyKeys(value)].map(key => {
|
||||
if (!('value' in Object.getOwnProperty(value, key))) {
|
||||
throw Error('Not a CONSTVALUE (found a setter or getter?)');
|
||||
}
|
||||
return {
|
||||
type: 'Property',
|
||||
kind: 'init',
|
||||
method: false,
|
||||
shorthand: false,
|
||||
computed: true,
|
||||
key: {
|
||||
type: 'Literal',
|
||||
value: key
|
||||
},
|
||||
value: TO_CONSTEXPR(value[key])
|
||||
}
|
||||
})),
|
||||
});
|
||||
}
|
||||
throw Error('Not a CONSTVALUE (did you pass a RegExp?)');
|
||||
};
|
||||
|
||||
// THIS DOES NOT HANDLE NODE SPECIFIC CASES LIKE IfStatement
|
||||
const FOLD_EMPTY = function*(path) {
|
||||
if (
|
||||
path &&
|
||||
path.node &&
|
||||
path.parent &&
|
||||
Array.isArray(path.parent.node) &&
|
||||
IS_EMPTY(path)
|
||||
) {
|
||||
REMOVE(path);
|
||||
return yield;
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
|
||||
// THIS DOES NOT HANDLE NODE SPECIFIC CASES LIKE IfStatement
|
||||
const FOLD_TEMPLATE = function*(path) {
|
||||
if (
|
||||
path &&
|
||||
path.node &&
|
||||
path.type === 'TemplateLiteral'
|
||||
) {
|
||||
let updated = false;
|
||||
for (let i = 0; i < path.node.exressions.length; i++) {
|
||||
if (IS_CONSTEXPR(path.node.expressions[i])) {
|
||||
//let
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_EXPR_STMT = function*(path) {
|
||||
// TODO: enforce completion value checking
|
||||
if (path && path.node && path.node.type === 'ExpressionStatement') {
|
||||
// merge all the adjacent expression statements into sequences
|
||||
if (Array.isArray(path.parent.node)) {
|
||||
// could have nodes after it
|
||||
const siblings = path.parent.node;
|
||||
if (!IS_PRAGMA(path)) {
|
||||
if (path.key < siblings.length - 1) {
|
||||
const mergeable = [path.node];
|
||||
for (let needle = path.key + 1; needle < siblings.length; needle++) {
|
||||
if (siblings[needle].type !== 'ExpressionStatement') {
|
||||
break;
|
||||
}
|
||||
mergeable.push(siblings[needle]);
|
||||
}
|
||||
if (mergeable.length > 1) {
|
||||
siblings.splice(path.key, mergeable.length, {
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'SequenceExpression',
|
||||
expressions: mergeable.reduce(
|
||||
(acc, es) => {
|
||||
if (es.expression.type == 'SequenceExpression') {
|
||||
return [...acc, ...es.expression.expressions];
|
||||
} else {
|
||||
return [...acc, es.expression];
|
||||
}
|
||||
},
|
||||
[]
|
||||
),
|
||||
},
|
||||
});
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IS_NOT_COMPLETION(path) && IS_CONSTEXPR(path.node.expression)) {
|
||||
return REPLACE(path, EMPTY);
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_WHILE = function*(path) {
|
||||
if (path && path.node) {
|
||||
if (path.node.type === 'DoWhileStatement') {
|
||||
console.error('FOLD_DOWHILE');
|
||||
REPLACE_IF_EMPTY(path.get(['body']), EMPTY);
|
||||
}
|
||||
if (path.node.type === 'WhileStatement') {
|
||||
console.error('FOLD_WHILE');
|
||||
let { test, consequent, alternate } = path.node;
|
||||
if (IS_CONSTEXPR(test)) {
|
||||
test = CONSTVALUE(test);
|
||||
if (!test) {
|
||||
return REPLACE(path, EMPTY);
|
||||
}
|
||||
}
|
||||
REPLACE_IF_EMPTY(path.get(['body']), EMPTY);
|
||||
}
|
||||
if (path.node.type === 'ForStatement') {
|
||||
console.error('FOLD_FOR');
|
||||
REPLACE_IF_EMPTY(path.get(['body']), EMPTY);
|
||||
let { init, test, update } = path.node;
|
||||
let updated = false;
|
||||
if (init && IS_CONSTEXPR(init)) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['init']), null);
|
||||
}
|
||||
if (test && IS_CONSTEXPR(test)) {
|
||||
let current = CONSTVALUE(test);
|
||||
let coerced = Boolean(current);
|
||||
// remove the test if it is always true
|
||||
if (coerced === true) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['test']), null);
|
||||
} else if (coerced !== current) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['test']), TO_CONSTEXPR(coerced));
|
||||
}
|
||||
}
|
||||
if (update && IS_CONSTEXPR(update)) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['update']), null);
|
||||
}
|
||||
if (updated) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_IF = function*(path) {
|
||||
if (path && path.node && path.node.type === 'IfStatement') {
|
||||
let { test, consequent, alternate } = path.node;
|
||||
const is_not_completion = IS_NOT_COMPLETION(path);
|
||||
if (is_not_completion && !alternate) {
|
||||
if (IS_EMPTY(path.get(['consequent']))) {
|
||||
console.error('FOLD_IF_EMPTY_CONSEQUENT');
|
||||
REPLACE(path, {
|
||||
type: 'ExpressionStatement',
|
||||
expression: test,
|
||||
});
|
||||
return path.parent;
|
||||
}
|
||||
}
|
||||
if (alternate) {
|
||||
if (alternate.type === consequent.type) {
|
||||
if (consequent.type === 'ExpressionStatement') {
|
||||
console.error('FOLD_IF_BOTH_EXPRSTMT');
|
||||
REPLACE(path, {
|
||||
type: 'ExpressionStatement', expression:
|
||||
{
|
||||
type: 'ConditionalExpression',
|
||||
test: test,
|
||||
consequent: consequent.expression,
|
||||
alternate: alternate.expression,
|
||||
}});
|
||||
return path.parent;
|
||||
}
|
||||
else if (consequent.type === 'ReturnStatement' ||
|
||||
consequent.type === 'ThrowStatement') {
|
||||
console.error('FOLD_IF_BOTH_COMPLETIONS');
|
||||
REPLACE(path, {
|
||||
type: 'ExpressionStatement', expression:{
|
||||
type: consequent.type,
|
||||
argument: {
|
||||
type: 'ConditionalExpression',
|
||||
test: test,
|
||||
consequent: consequent.argument,
|
||||
alternate: alternate.argument,
|
||||
}}
|
||||
});
|
||||
return path.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (is_not_completion && consequent.type === 'ExpressionStatement') {
|
||||
console.error('FOLD_IF_NON_COMPLETION_TO_&&');
|
||||
REPLACE(path, {
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'BinaryExpression',
|
||||
operator: '&&',
|
||||
left: test,
|
||||
right: consequent.expression,
|
||||
}
|
||||
});
|
||||
return path.parent;
|
||||
}
|
||||
if (IS_CONSTEXPR(test)) {
|
||||
test = CONSTVALUE(test);
|
||||
if (test) {
|
||||
return REPLACE(path, consequent);
|
||||
}
|
||||
if (alternate) {
|
||||
return REPLACE(path, alternate);
|
||||
}
|
||||
return REPLACE(path, EMPTY);
|
||||
}
|
||||
consequent = path.get(['consequent']);
|
||||
let updated;
|
||||
if (consequent.node !== EMPTY) {
|
||||
REPLACE_IF_EMPTY(consequent, EMPTY);
|
||||
if (consequent.parent.node[consequent.key] === EMPTY) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (alternate) {
|
||||
alternate = path.get(['alternate']);
|
||||
REMOVE_IF_EMPTY(alternate);
|
||||
if (path.node.alternate === null) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_SEQUENCE = function*(path) {
|
||||
if (path && path.node && path.node.type === 'SequenceExpression') {
|
||||
console.error('FOLD_SEQUENCE');
|
||||
// never delete the last value
|
||||
for (let i = 0; i < path.node.expressions.length - 1; i++) {
|
||||
if (IS_CONSTEXPR(path.node.expressions[i])) {
|
||||
path.node.expressions.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (path.node.expressions.length === 1) {
|
||||
return REPLACE(path, path.node.expressions[0]);
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_LOGICAL = function*(path) {
|
||||
if (path && path.node && path.node.type === 'LogicalExpression') {
|
||||
console.error('FOLD_LOGICAL');
|
||||
let { left, right, operator } = path.node;
|
||||
if (IS_CONSTEXPR(left)) {
|
||||
left = CONSTVALUE(left);
|
||||
if (operator === '||') {
|
||||
if (left) {
|
||||
return REPLACE(path, TO_CONSTEXPR(left));
|
||||
}
|
||||
return REPLACE(path, right);
|
||||
} else if (operator === '&&') {
|
||||
if (!left) {
|
||||
return REPLACE(path, TO_CONSTEXPR(left));
|
||||
}
|
||||
return REPLACE(path, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_SWITCH = function*(path) {
|
||||
if (path && path.node && path.node.type === 'SwitchStatement') {
|
||||
let { discriminant, cases } = path.node;
|
||||
// if there are no cases, just become an expression
|
||||
if (cases.length === 0 && IS_NOT_COMPLETION(path)) {
|
||||
return REPLACE(path, {
|
||||
type: 'ExpressionStatement',
|
||||
expression: discriminant
|
||||
});
|
||||
}
|
||||
// if the discriminant is static
|
||||
// remove any preceding non-matching static cases
|
||||
// fold any trailing cases into the matching case
|
||||
if (cases.length > 1 && IS_CONSTEXPR(discriminant)) {
|
||||
const discriminant_value = CONSTVALUE(discriminant);
|
||||
for (var i = 0; i < cases.length; i++) {
|
||||
const test = cases[i].test;
|
||||
if (IS_CONSTEXPR(test)) {
|
||||
let test_value = CONSTVALUE(test);
|
||||
if (discriminant_value === test_value) {
|
||||
let new_consequent = cases[i].consequent;
|
||||
if (i < cases.length - 1) {
|
||||
for (let fallthrough of cases.slice(i+1)) {
|
||||
new_consequent.push(...fallthrough.consequent);
|
||||
}
|
||||
}
|
||||
cases[i].consequent = new_consequent;
|
||||
REPLACE(path.get(['cases']), [cases[i]]);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we had a dynamic case need to bail
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_UNREACHABLE = function*(path) {
|
||||
if (path && path.node && path.parent && Array.isArray(path.parent.node)) {
|
||||
if (path.node.type === 'ReturnStatement' ||
|
||||
path.node.type === 'ContinueStatement' ||
|
||||
path.node.type === 'BreakStatement' ||
|
||||
path.node.type === 'ThrowStatement') {
|
||||
const next_key = path.key + 1;
|
||||
path.parent.node.splice(next_key, path.parent.node.length - next_key);
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
}
|
||||
const FOLD_CONDITIONAL = function*(path) {
|
||||
if (path && path.node && path.node.type === 'ConditionalExpression') {
|
||||
console.error('FOLD_CONDITIONAL');
|
||||
let { test, consequent, alternate } = path.node;
|
||||
if (IS_CONSTEXPR(test)) {
|
||||
test = CONSTVALUE(test);
|
||||
if (test) {
|
||||
return REPLACE(path, consequent);
|
||||
}
|
||||
return REPLACE(path, alternate);
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_BINARY = function*(path) {
|
||||
if (
|
||||
path &&
|
||||
path.node &&
|
||||
path.node.type === 'BinaryExpression' &&
|
||||
!IS_NAN(path.node)
|
||||
) {
|
||||
console.error('FOLD_BINARY');
|
||||
let { left, right, operator } = path.node;
|
||||
if (operator === '==' || operator === '!=') {
|
||||
let updated = false;
|
||||
if (IS_UNDEFINED(left)) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['left']), NULL);
|
||||
}
|
||||
if (IS_UNDEFINED(right)) {
|
||||
updated = true;
|
||||
REPLACE(path.get(['right']), NULL);
|
||||
}
|
||||
if (updated) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
if (path.node !== INFINITY && path.node !== NEG_INFINITY && IS_CONSTEXPR(left) && IS_CONSTEXPR(right)) {
|
||||
left = CONSTVALUE(left);
|
||||
right = CONSTVALUE(right);
|
||||
let value;
|
||||
if ((!left || typeof left !== 'object') && (!right || typeof right !== 'object')) {
|
||||
if (operator === '+') {
|
||||
value = left + right;
|
||||
} else if (operator === '-') {
|
||||
value = left - right;
|
||||
} else if (operator === '*') {
|
||||
value = left * right;
|
||||
} else if (operator === '/') {
|
||||
value = left / right;
|
||||
} else if (operator === '%') {
|
||||
value = left % right;
|
||||
} else if (operator === '==') {
|
||||
value = left == right;
|
||||
} else if (operator === '!=') {
|
||||
value = left != right;
|
||||
} else if (operator === '===') {
|
||||
value = left === right;
|
||||
} else if (operator === '!==') {
|
||||
value = left !== right;
|
||||
} else if (operator === '<') {
|
||||
value = left < right;
|
||||
} else if (operator === '<=') {
|
||||
value = left <= right;
|
||||
} else if (operator === '>') {
|
||||
value = left > right;
|
||||
} else if (operator === '>=') {
|
||||
value = left >= right;
|
||||
} else if (operator === '<<') {
|
||||
value = left << right;
|
||||
} else if (operator === '>>') {
|
||||
value = left >> right;
|
||||
} else if (operator === '>>>') {
|
||||
value = left >>> right;
|
||||
} else if (operator === '|') {
|
||||
value = left | right;
|
||||
} else if (operator === '&') {
|
||||
value = left & right;
|
||||
} else if (operator === '^') {
|
||||
value = left ^ right;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (operator === '==') value = false;
|
||||
if (operator === '===') value = false;
|
||||
if (operator === '!=') value = true;
|
||||
if (operator === '!==') value = true;
|
||||
if (operator === 'in' && typeof right === 'object' && right) {
|
||||
value = Boolean(Object.getOwnPropertyDescriptor(right, left));
|
||||
}
|
||||
}
|
||||
if (value !== void 0) {
|
||||
if (typeof value === 'string' || typeof value === 'boolean' || value === null) {
|
||||
return REPLACE(path, TO_CONSTEXPR(value));
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return REPLACE(path, TO_CONSTEXPR(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_UNARY = function*(path) {
|
||||
if (path && path.node && path.node.type === 'UnaryExpression') {
|
||||
console.error('FOLD_UNARY');
|
||||
if (IS_CONSTEXPR(path.node)) {
|
||||
return yield path;
|
||||
}
|
||||
let { argument, operator } = path.node;
|
||||
if (IS_CONSTEXPR(argument)) {
|
||||
if (operator === 'void') {
|
||||
return REPLACE(path, UNDEFINED);
|
||||
}
|
||||
let value = CONSTVALUE(argument);
|
||||
if (operator === '-') {
|
||||
value = -value;
|
||||
} else if (operator === '+') {
|
||||
value = +value;
|
||||
} else if (operator === '~') {
|
||||
value = ~value;
|
||||
} else if (operator === '!') {
|
||||
value = !value;
|
||||
} else if (operator === 'typeof') {
|
||||
value = typeof value;
|
||||
} else if (operator === 'delete') {
|
||||
value = true;
|
||||
}
|
||||
return REPLACE(path, TO_CONSTEXPR(value));
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
const FOLD_EVAL = function*(path) {
|
||||
if (path && path.node && path.node.type === 'CallExpression' &&
|
||||
path.node.callee.type === 'Identifier' && path.node.callee.name === 'eval') {
|
||||
console.error('FOLD_EVAL');
|
||||
if (path.node.arguments.length === 1 && path.node.arguments[0].type === 'Literal') {
|
||||
let result = esprima.parse(`${
|
||||
CONSTVALUE(path.node.arguments[0])
|
||||
}`);
|
||||
if (result.body.length === 1 && result.body[0].type === 'ExpressionStatement') {
|
||||
return REPLACE(path, result.body[0].expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
}
|
||||
const FOLD_MEMBER = function*(path) {
|
||||
if (path && path.node && path.node.type === 'MemberExpression') {
|
||||
console.error('FOLD_MEMBER');
|
||||
if (path.node.computed && path.node.property.type === 'Literal') {
|
||||
const current = `${CONSTVALUE(path.node.property)}`;
|
||||
if (typeof current === 'string' && /^[$_a-z][$_a-z\d]*$/i.test(current)) {
|
||||
path.node.computed = false;
|
||||
path.node.property = {
|
||||
type: 'Identifier',
|
||||
name: current,
|
||||
};
|
||||
return path;
|
||||
}
|
||||
}
|
||||
if (IS_CONSTEXPR(path.node.object)) {
|
||||
const value = CONSTVALUE(path.node.object);
|
||||
if (typeof value === 'string' || Array.isArray(value) || (value && typeof value === 'object')) {
|
||||
let key;
|
||||
if (IS_CONSTEXPR(path.node.property)) {
|
||||
key = `${CONSTVALUE(path.node.property)}`;
|
||||
}
|
||||
else if (!path.node.computed) {
|
||||
key = path.node.property.name;
|
||||
}
|
||||
if (key !== void 0) {
|
||||
const desc = Object.getOwnPropertyDescriptor(value, key);
|
||||
if (desc) {
|
||||
const folded = value[key];
|
||||
console.error('FOLDING', JSON.stringify(folded));
|
||||
if (IN_PRAGMA_POS(path) && typeof folded === 'string') {
|
||||
if (value.length > 1) {
|
||||
REPLACE(
|
||||
path.get(['object']),
|
||||
TO_CONSTEXPR(value.slice(key, key + 1))
|
||||
);
|
||||
REPLACE(path.get(['property']), TO_CONSTEXPR(0));
|
||||
return path;
|
||||
}
|
||||
} else {
|
||||
return REPLACE(path, TO_CONSTEXPR(value[key]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
};
|
||||
|
||||
const $MIN = Symbol();
|
||||
const MIN_TRUE = Object.freeze({
|
||||
[$MIN]: true,
|
||||
type: 'UnaryExpression',
|
||||
operator: '!',
|
||||
argument: Object.freeze({
|
||||
[$MIN]: true,
|
||||
type: 'Literal',
|
||||
value: 0
|
||||
})
|
||||
});
|
||||
const MIN_FALSE = Object.freeze({
|
||||
[$MIN]: true,
|
||||
type: 'UnaryExpression',
|
||||
operator: '!',
|
||||
argument: Object.freeze({
|
||||
[$MIN]: true,
|
||||
type: 'Literal',
|
||||
value: 1
|
||||
})
|
||||
});
|
||||
const MIN_REPLACEMENTS = new Map;
|
||||
MIN_REPLACEMENTS.set(true, MIN_TRUE);
|
||||
MIN_REPLACEMENTS.set(false, MIN_FALSE);
|
||||
const MIN_VALUES = function*(path) {
|
||||
if (path && path.node && !path.node[$MIN] && IS_CONSTEXPR(path.node)) {
|
||||
let value = CONSTVALUE(path.node);
|
||||
if (MIN_REPLACEMENTS.has(value)) {
|
||||
console.error('MIN_VALUE', value)
|
||||
return REPLACE(path, MIN_REPLACEMENTS.get(value));
|
||||
}
|
||||
}
|
||||
return yield path;
|
||||
}
|
||||
|
||||
import esprima from 'esprima';
|
||||
import util from 'util';
|
||||
import escodegen from 'escodegen';
|
||||
const optimize = (src) => {
|
||||
const ROOT = new NodePath(
|
||||
null,
|
||||
esprima.parse(
|
||||
src,
|
||||
{
|
||||
// loc: true,
|
||||
// source: '<stdin>',
|
||||
}
|
||||
),
|
||||
null
|
||||
);
|
||||
// all of these are things that could affect completion value positions
|
||||
const walk_expressions = WalkCombinator.pipe(
|
||||
...[
|
||||
WalkCombinator.DEPTH_FIRST,
|
||||
{
|
||||
// We never work on Arrays
|
||||
*inputs(path) {
|
||||
if (Array.isArray(path)) return;
|
||||
return yield path;
|
||||
},
|
||||
},
|
||||
{ inputs: FOLD_UNREACHABLE },
|
||||
{ inputs: FOLD_IF },
|
||||
{ inputs: FOLD_SWITCH },
|
||||
{ inputs: FOLD_EXPR_STMT },
|
||||
{ inputs: FOLD_CONDITIONAL },
|
||||
{ inputs: FOLD_LOGICAL },
|
||||
{ inputs: FOLD_BINARY },
|
||||
{ inputs: FOLD_UNARY },
|
||||
{ inputs: FOLD_SEQUENCE },
|
||||
{ inputs: FOLD_MEMBER },
|
||||
{ inputs: FOLD_EMPTY },
|
||||
{ inputs: FOLD_WHILE },
|
||||
{ inputs: FOLD_EVAL },
|
||||
]
|
||||
).walk(ROOT);
|
||||
for (const _ of walk_expressions) {
|
||||
}
|
||||
const minify = WalkCombinator.pipe(
|
||||
...[
|
||||
WalkCombinator.DEPTH_FIRST,
|
||||
{
|
||||
// We never work on Arrays
|
||||
*inputs(path) {
|
||||
if (Array.isArray(path)) return;
|
||||
return yield path;
|
||||
},
|
||||
},
|
||||
{ inputs: MIN_VALUES },
|
||||
]
|
||||
).walk(ROOT);
|
||||
for (const _ of minify) {
|
||||
}
|
||||
return ROOT;
|
||||
}
|
||||
import mississippi from 'mississippi';
|
||||
process.stdin.pipe(
|
||||
mississippi.concat(buff => {
|
||||
const ROOT = optimize(`${buff}`)
|
||||
console.error(
|
||||
'%s',
|
||||
util.inspect(ROOT.node, {
|
||||
depth: null,
|
||||
colors: true,
|
||||
})
|
||||
);
|
||||
const out = escodegen.generate(ROOT.node);
|
||||
console.log(out);
|
||||
})
|
||||
);
|
||||
6
samples/JavaScript/entry.mjs
Normal file
6
samples/JavaScript/entry.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
import bar from './module.mjs';
|
||||
function foo() {
|
||||
return "I am foo";
|
||||
}
|
||||
export {foo};
|
||||
console.log(bar);
|
||||
5
samples/JavaScript/module.mjs
Normal file
5
samples/JavaScript/module.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import {foo} from './entry.mjs';
|
||||
console.log(foo());
|
||||
|
||||
const bar = "I am bar.";
|
||||
export {bar as default};
|
||||
Reference in New Issue
Block a user