1006 lines
25 KiB
JavaScript
1006 lines
25 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
/**
|
|
* Transform match expressions and statements.
|
|
*/
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.transformProgram = transformProgram;
|
|
|
|
var _SimpleTransform = require("../transform/SimpleTransform");
|
|
|
|
var _astNodeMutationHelpers = require("../transform/astNodeMutationHelpers");
|
|
|
|
var _createSyntaxError = require("../utils/createSyntaxError");
|
|
|
|
var _Builders = require("../utils/Builders");
|
|
|
|
var _GenID = require("../utils/GenID");
|
|
|
|
/**
|
|
* Generated identifiers.
|
|
* `GenID` is initialized in the transform.
|
|
*/
|
|
let GenID = null;
|
|
|
|
function genIdent() {
|
|
if (GenID == null) {
|
|
throw Error('GenID must be initialized at the start of the transform.');
|
|
}
|
|
|
|
return (0, _Builders.ident)(GenID.genID());
|
|
}
|
|
/**
|
|
* A series of properties.
|
|
* When combined with the match argument (the root expression), provides the
|
|
* location of to be tested against, or location to be extracted to a binding.
|
|
*/
|
|
|
|
|
|
function objKeyToString(node) {
|
|
switch (node.type) {
|
|
case 'Identifier':
|
|
return node.name;
|
|
|
|
case 'Literal':
|
|
{
|
|
const {
|
|
value
|
|
} = node;
|
|
|
|
if (typeof value === 'number') {
|
|
return String(value);
|
|
} else if (typeof value === 'string') {
|
|
return value;
|
|
} else {
|
|
return node.raw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function convertMemberPattern(pattern) {
|
|
const {
|
|
base,
|
|
property,
|
|
loc,
|
|
range
|
|
} = pattern;
|
|
const object = base.type === 'MatchIdentifierPattern' ? base.id : convertMemberPattern(base);
|
|
|
|
if (property.type === 'Identifier') {
|
|
return {
|
|
type: 'MemberExpression',
|
|
object,
|
|
property,
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)({
|
|
loc,
|
|
range
|
|
})
|
|
};
|
|
} else {
|
|
return {
|
|
type: 'MemberExpression',
|
|
object,
|
|
property,
|
|
computed: true,
|
|
optional: false,
|
|
...(0, _Builders.etc)({
|
|
loc,
|
|
range
|
|
})
|
|
};
|
|
}
|
|
}
|
|
|
|
function checkDuplicateBindingName(seenBindingNames, node, name) {
|
|
if (seenBindingNames.has(name)) {
|
|
throw (0, _createSyntaxError.createSyntaxError)(node, `Duplicate variable name '${name}' in match case pattern.`);
|
|
}
|
|
|
|
seenBindingNames.add(name);
|
|
}
|
|
|
|
function checkBindingKind(node, kind) {
|
|
if (kind === 'var') {
|
|
throw (0, _createSyntaxError.createSyntaxError)(node, `'var' bindings are not allowed. Use 'const' or 'let'.`);
|
|
}
|
|
}
|
|
/**
|
|
* Does an object property's pattern require a `prop-exists` condition added?
|
|
* If the pattern is a literal like `0`, then it's not required, since the `eq`
|
|
* condition implies the prop exists. However, if we could be doing an equality
|
|
* check against `undefined`, then it is required, since that will be true even
|
|
* if the property doesn't exist.
|
|
*/
|
|
|
|
|
|
function needsPropExistsCond(pattern) {
|
|
switch (pattern.type) {
|
|
case 'MatchWildcardPattern':
|
|
case 'MatchBindingPattern':
|
|
case 'MatchIdentifierPattern':
|
|
case 'MatchMemberPattern':
|
|
return true;
|
|
|
|
case 'MatchLiteralPattern':
|
|
case 'MatchUnaryPattern':
|
|
case 'MatchObjectPattern':
|
|
case 'MatchArrayPattern':
|
|
return false;
|
|
|
|
case 'MatchAsPattern':
|
|
{
|
|
const {
|
|
pattern: asPattern
|
|
} = pattern;
|
|
return needsPropExistsCond(asPattern);
|
|
}
|
|
|
|
case 'MatchOrPattern':
|
|
{
|
|
const {
|
|
patterns
|
|
} = pattern;
|
|
return patterns.some(needsPropExistsCond);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Analyzes a match pattern, and produced both the conditions and bindings
|
|
* produced by that pattern.
|
|
*/
|
|
|
|
|
|
function analyzePattern(pattern, key, seenBindingNames) {
|
|
switch (pattern.type) {
|
|
case 'MatchWildcardPattern':
|
|
{
|
|
return {
|
|
conditions: [],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
case 'MatchLiteralPattern':
|
|
{
|
|
const {
|
|
literal
|
|
} = pattern;
|
|
const condition = {
|
|
type: 'eq',
|
|
key,
|
|
arg: literal
|
|
};
|
|
return {
|
|
conditions: [condition],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
case 'MatchUnaryPattern':
|
|
{
|
|
const {
|
|
operator,
|
|
argument,
|
|
loc,
|
|
range
|
|
} = pattern;
|
|
|
|
if (argument.value === 0) {
|
|
// We haven't decided whether we will compare these using `===` or `Object.is`
|
|
throw (0, _createSyntaxError.createSyntaxError)(pattern, `'+0' and '-0' are not yet supported in match unary patterns.`);
|
|
}
|
|
|
|
const arg = {
|
|
type: 'UnaryExpression',
|
|
operator,
|
|
argument,
|
|
prefix: true,
|
|
...(0, _Builders.etc)({
|
|
loc,
|
|
range
|
|
})
|
|
};
|
|
const condition = {
|
|
type: 'eq',
|
|
key,
|
|
arg
|
|
};
|
|
return {
|
|
conditions: [condition],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
case 'MatchIdentifierPattern':
|
|
{
|
|
const {
|
|
id
|
|
} = pattern;
|
|
const condition = id.name === 'NaN' ? {
|
|
type: 'is-nan',
|
|
key
|
|
} : {
|
|
type: 'eq',
|
|
key,
|
|
arg: id
|
|
};
|
|
return {
|
|
conditions: [condition],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
case 'MatchMemberPattern':
|
|
{
|
|
const arg = convertMemberPattern(pattern);
|
|
const condition = {
|
|
type: 'eq',
|
|
key,
|
|
arg
|
|
};
|
|
return {
|
|
conditions: [condition],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
case 'MatchBindingPattern':
|
|
{
|
|
const {
|
|
id,
|
|
kind
|
|
} = pattern;
|
|
checkDuplicateBindingName(seenBindingNames, pattern, id.name);
|
|
checkBindingKind(pattern, kind);
|
|
const binding = {
|
|
type: 'id',
|
|
key,
|
|
kind,
|
|
id
|
|
};
|
|
return {
|
|
conditions: [],
|
|
bindings: [binding]
|
|
};
|
|
}
|
|
|
|
case 'MatchAsPattern':
|
|
{
|
|
const {
|
|
pattern: asPattern,
|
|
target
|
|
} = pattern;
|
|
|
|
if (asPattern.type === 'MatchBindingPattern') {
|
|
throw (0, _createSyntaxError.createSyntaxError)(pattern, `Match 'as' patterns are not allowed directly on binding patterns.`);
|
|
}
|
|
|
|
const {
|
|
conditions,
|
|
bindings
|
|
} = analyzePattern(asPattern, key, seenBindingNames);
|
|
const [id, kind] = target.type === 'MatchBindingPattern' ? [target.id, target.kind] : [target, 'const'];
|
|
checkDuplicateBindingName(seenBindingNames, pattern, id.name);
|
|
checkBindingKind(pattern, kind);
|
|
const binding = {
|
|
type: 'id',
|
|
key,
|
|
kind,
|
|
id
|
|
};
|
|
return {
|
|
conditions,
|
|
bindings: bindings.concat(binding)
|
|
};
|
|
}
|
|
|
|
case 'MatchArrayPattern':
|
|
{
|
|
const {
|
|
elements,
|
|
rest
|
|
} = pattern;
|
|
const lengthOp = rest == null ? 'eq' : 'gte';
|
|
const conditions = [{
|
|
type: 'array',
|
|
key,
|
|
length: elements.length,
|
|
lengthOp
|
|
}];
|
|
const bindings = [];
|
|
elements.forEach((element, i) => {
|
|
const elementKey = key.concat((0, _Builders.numberLiteral)(i));
|
|
const {
|
|
conditions: childConditions,
|
|
bindings: childBindings
|
|
} = analyzePattern(element, elementKey, seenBindingNames);
|
|
conditions.push(...childConditions);
|
|
bindings.push(...childBindings);
|
|
});
|
|
|
|
if (rest != null && rest.argument != null) {
|
|
const {
|
|
id,
|
|
kind
|
|
} = rest.argument;
|
|
checkDuplicateBindingName(seenBindingNames, rest.argument, id.name);
|
|
checkBindingKind(pattern, kind);
|
|
bindings.push({
|
|
type: 'array-rest',
|
|
key,
|
|
exclude: elements.length,
|
|
kind,
|
|
id
|
|
});
|
|
}
|
|
|
|
return {
|
|
conditions,
|
|
bindings
|
|
};
|
|
}
|
|
|
|
case 'MatchObjectPattern':
|
|
{
|
|
const {
|
|
properties,
|
|
rest
|
|
} = pattern;
|
|
const conditions = [{
|
|
type: 'object',
|
|
key
|
|
}];
|
|
const bindings = [];
|
|
const objKeys = [];
|
|
const seenNames = new Set();
|
|
properties.forEach(prop => {
|
|
const {
|
|
key: objKey,
|
|
pattern: propPattern
|
|
} = prop;
|
|
objKeys.push(objKey);
|
|
const name = objKeyToString(objKey);
|
|
|
|
if (seenNames.has(name)) {
|
|
throw (0, _createSyntaxError.createSyntaxError)(propPattern, `Duplicate property name '${name}' in match object pattern.`);
|
|
}
|
|
|
|
seenNames.add(name);
|
|
const propKey = key.concat(objKey);
|
|
|
|
if (needsPropExistsCond(propPattern)) {
|
|
conditions.push({
|
|
type: 'prop-exists',
|
|
key,
|
|
propName: name
|
|
});
|
|
}
|
|
|
|
const {
|
|
conditions: childConditions,
|
|
bindings: childBindings
|
|
} = analyzePattern(propPattern, propKey, seenBindingNames);
|
|
conditions.push(...childConditions);
|
|
bindings.push(...childBindings);
|
|
});
|
|
|
|
if (rest != null && rest.argument != null) {
|
|
const {
|
|
id,
|
|
kind
|
|
} = rest.argument;
|
|
checkDuplicateBindingName(seenBindingNames, rest.argument, id.name);
|
|
checkBindingKind(pattern, kind);
|
|
bindings.push({
|
|
type: 'object-rest',
|
|
key,
|
|
exclude: objKeys,
|
|
kind,
|
|
id
|
|
});
|
|
}
|
|
|
|
return {
|
|
conditions,
|
|
bindings
|
|
};
|
|
}
|
|
|
|
case 'MatchOrPattern':
|
|
{
|
|
const {
|
|
patterns
|
|
} = pattern;
|
|
let hasWildcard = false;
|
|
const orConditions = patterns.map(subpattern => {
|
|
const {
|
|
conditions,
|
|
bindings
|
|
} = analyzePattern(subpattern, key, seenBindingNames);
|
|
|
|
if (bindings.length > 0) {
|
|
// We will implement this in the future.
|
|
throw (0, _createSyntaxError.createSyntaxError)(pattern, `Bindings in match 'or' patterns are not yet supported.`);
|
|
}
|
|
|
|
if (conditions.length === 0) {
|
|
hasWildcard = true;
|
|
}
|
|
|
|
return conditions;
|
|
});
|
|
|
|
if (hasWildcard) {
|
|
return {
|
|
conditions: [],
|
|
bindings: []
|
|
};
|
|
}
|
|
|
|
return {
|
|
conditions: [{
|
|
type: 'or',
|
|
orConditions
|
|
}],
|
|
bindings: []
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function expressionOfKey(root, key) {
|
|
return key.reduce((acc, prop) => prop.type === 'Identifier' ? {
|
|
type: 'MemberExpression',
|
|
object: acc,
|
|
property: (0, _astNodeMutationHelpers.shallowCloneNode)(prop),
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
} : {
|
|
type: 'MemberExpression',
|
|
object: acc,
|
|
property: (0, _astNodeMutationHelpers.shallowCloneNode)(prop),
|
|
computed: true,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
}, (0, _astNodeMutationHelpers.deepCloneNode)(root));
|
|
}
|
|
|
|
function testsOfCondition(root, condition) {
|
|
switch (condition.type) {
|
|
case 'eq':
|
|
{
|
|
// <x> === <arg>
|
|
const {
|
|
key,
|
|
arg
|
|
} = condition;
|
|
return [{
|
|
type: 'BinaryExpression',
|
|
left: expressionOfKey(root, key),
|
|
right: arg,
|
|
operator: '===',
|
|
...(0, _Builders.etc)()
|
|
}];
|
|
}
|
|
|
|
case 'is-nan':
|
|
{
|
|
// Number.isNaN(<x>)
|
|
const {
|
|
key
|
|
} = condition;
|
|
const callee = {
|
|
type: 'MemberExpression',
|
|
object: (0, _Builders.ident)('Number'),
|
|
property: (0, _Builders.ident)('isNaN'),
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
return [(0, _Builders.callExpression)(callee, [expressionOfKey(root, key)])];
|
|
}
|
|
|
|
case 'array':
|
|
{
|
|
// Array.isArray(<x>) && <x>.length === <length>
|
|
const {
|
|
key,
|
|
length,
|
|
lengthOp
|
|
} = condition;
|
|
const operator = lengthOp === 'eq' ? '===' : '>=';
|
|
const isArray = (0, _Builders.callExpression)({
|
|
type: 'MemberExpression',
|
|
object: (0, _Builders.ident)('Array'),
|
|
property: (0, _Builders.ident)('isArray'),
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
}, [expressionOfKey(root, key)]);
|
|
const lengthCheck = {
|
|
type: 'BinaryExpression',
|
|
left: {
|
|
type: 'MemberExpression',
|
|
object: expressionOfKey(root, key),
|
|
property: (0, _Builders.ident)('length'),
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
},
|
|
right: (0, _Builders.numberLiteral)(length),
|
|
operator,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
return [isArray, lengthCheck];
|
|
}
|
|
|
|
case 'object':
|
|
{
|
|
// (typeof <x> === 'object' && <x> !== null) || typeof <x> === 'function'
|
|
const {
|
|
key
|
|
} = condition;
|
|
const typeofObject = (0, _Builders.typeofExpression)(expressionOfKey(root, key), 'object');
|
|
const typeofFunction = (0, _Builders.typeofExpression)(expressionOfKey(root, key), 'function');
|
|
const notNull = {
|
|
type: 'BinaryExpression',
|
|
left: expressionOfKey(root, key),
|
|
right: (0, _Builders.nullLiteral)(),
|
|
operator: '!==',
|
|
...(0, _Builders.etc)()
|
|
};
|
|
return [(0, _Builders.disjunction)([(0, _Builders.conjunction)([typeofObject, notNull]), typeofFunction])];
|
|
}
|
|
|
|
case 'prop-exists':
|
|
{
|
|
// <propName> in <x>
|
|
const {
|
|
key,
|
|
propName
|
|
} = condition;
|
|
const inObject = {
|
|
type: 'BinaryExpression',
|
|
left: (0, _Builders.stringLiteral)(propName),
|
|
right: expressionOfKey(root, key),
|
|
operator: 'in',
|
|
...(0, _Builders.etc)()
|
|
};
|
|
return [inObject];
|
|
}
|
|
|
|
case 'or':
|
|
{
|
|
// <a> || <b> || ...
|
|
const {
|
|
orConditions
|
|
} = condition;
|
|
const tests = orConditions.map(conditions => (0, _Builders.conjunction)(testsOfConditions(root, conditions)));
|
|
return [(0, _Builders.disjunction)(tests)];
|
|
}
|
|
}
|
|
}
|
|
|
|
function testsOfConditions(root, conditions) {
|
|
return conditions.flatMap(condition => testsOfCondition(root, condition));
|
|
}
|
|
|
|
function statementsOfBindings(root, bindings) {
|
|
return bindings.map(binding => {
|
|
switch (binding.type) {
|
|
case 'id':
|
|
{
|
|
// const <id> = <x>;
|
|
const {
|
|
key,
|
|
kind,
|
|
id
|
|
} = binding;
|
|
return (0, _Builders.variableDeclaration)(kind, id, expressionOfKey(root, key));
|
|
}
|
|
|
|
case 'array-rest':
|
|
{
|
|
// const <id> = <x>.slice(<exclude>);
|
|
const {
|
|
key,
|
|
kind,
|
|
id,
|
|
exclude
|
|
} = binding;
|
|
const init = (0, _Builders.callExpression)({
|
|
type: 'MemberExpression',
|
|
object: expressionOfKey(root, key),
|
|
property: (0, _Builders.ident)('slice'),
|
|
computed: false,
|
|
optional: false,
|
|
...(0, _Builders.etc)()
|
|
}, [(0, _Builders.numberLiteral)(exclude)]);
|
|
return (0, _Builders.variableDeclaration)(kind, id, init);
|
|
}
|
|
|
|
case 'object-rest':
|
|
{
|
|
// const {a: _, b: _, ...<id>} = <x>;
|
|
const {
|
|
key,
|
|
kind,
|
|
id,
|
|
exclude
|
|
} = binding;
|
|
const destructuring = {
|
|
type: 'ObjectPattern',
|
|
properties: exclude.map(prop => prop.type === 'Identifier' ? {
|
|
type: 'Property',
|
|
key: (0, _astNodeMutationHelpers.shallowCloneNode)(prop),
|
|
value: genIdent(),
|
|
kind: 'init',
|
|
computed: false,
|
|
method: false,
|
|
shorthand: false,
|
|
...(0, _Builders.etc)(),
|
|
parent: _Builders.EMPTY_PARENT
|
|
} : {
|
|
type: 'Property',
|
|
key: (0, _astNodeMutationHelpers.shallowCloneNode)(prop),
|
|
value: genIdent(),
|
|
kind: 'init',
|
|
computed: true,
|
|
method: false,
|
|
shorthand: false,
|
|
...(0, _Builders.etc)(),
|
|
parent: _Builders.EMPTY_PARENT
|
|
}).concat({
|
|
type: 'RestElement',
|
|
argument: id,
|
|
...(0, _Builders.etc)()
|
|
}),
|
|
typeAnnotation: null,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
return (0, _Builders.variableDeclaration)(kind, destructuring, expressionOfKey(root, key));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* For throwing an error if no cases are matched.
|
|
*/
|
|
|
|
|
|
const fallthroughErrorMsgText = `Match: No case succesfully matched. Make exhaustive or add a wildcard case using '_'.`;
|
|
|
|
function fallthroughErrorMsg(value) {
|
|
return {
|
|
type: 'BinaryExpression',
|
|
operator: '+',
|
|
left: (0, _Builders.stringLiteral)(`${fallthroughErrorMsgText} Argument: `),
|
|
right: value,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
}
|
|
|
|
function fallthroughError(value) {
|
|
return (0, _Builders.throwStatement)(fallthroughErrorMsg(value));
|
|
}
|
|
/**
|
|
* If the argument has no side-effects (ignoring getters). Either an identifier
|
|
* or member expression with identifier root and non-computed/literal properties.
|
|
*/
|
|
|
|
|
|
function calculateSimpleArgument(node) {
|
|
switch (node.type) {
|
|
case 'Identifier':
|
|
case 'Super':
|
|
return true;
|
|
|
|
case 'MemberExpression':
|
|
{
|
|
const {
|
|
object,
|
|
property,
|
|
computed
|
|
} = node;
|
|
|
|
if (computed && property.type !== 'Literal') {
|
|
return false;
|
|
}
|
|
|
|
return calculateSimpleArgument(object);
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Analyze the match cases and return information we will use to build the result.
|
|
*/
|
|
|
|
|
|
function analyzeCases(cases) {
|
|
let hasBindings = false;
|
|
let hasWildcard = false;
|
|
const analyses = [];
|
|
|
|
for (let i = 0; i < cases.length; i++) {
|
|
const {
|
|
pattern,
|
|
guard,
|
|
body
|
|
} = cases[i];
|
|
const {
|
|
conditions,
|
|
bindings
|
|
} = analyzePattern(pattern, [], new Set());
|
|
hasBindings = hasBindings || bindings.length > 0;
|
|
analyses.push({
|
|
conditions,
|
|
bindings,
|
|
guard,
|
|
body
|
|
}); // This case catches everything, no reason to continue.
|
|
|
|
if (conditions.length === 0 && guard == null) {
|
|
hasWildcard = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
hasBindings,
|
|
hasWildcard,
|
|
analyses
|
|
};
|
|
}
|
|
/**
|
|
* Match expression transform entry point.
|
|
*/
|
|
|
|
|
|
function mapMatchExpression(node) {
|
|
const {
|
|
argument,
|
|
cases
|
|
} = node;
|
|
const {
|
|
hasBindings,
|
|
hasWildcard,
|
|
analyses
|
|
} = analyzeCases(cases);
|
|
const isSimpleArgument = !hasBindings && calculateSimpleArgument(argument);
|
|
const genRoot = !isSimpleArgument ? genIdent() : null;
|
|
const root = genRoot == null ? argument : genRoot; // No bindings and a simple argument means we can use nested conditional
|
|
// expressions.
|
|
|
|
if (isSimpleArgument) {
|
|
const wildcardAnalaysis = hasWildcard ? analyses.pop() : null;
|
|
const lastBody = wildcardAnalaysis != null ? wildcardAnalaysis.body : (0, _Builders.iife)([fallthroughError((0, _astNodeMutationHelpers.shallowCloneNode)(root))]);
|
|
return analyses.reverse().reduce((acc, analysis) => {
|
|
const {
|
|
conditions,
|
|
guard,
|
|
body
|
|
} = analysis;
|
|
const tests = testsOfConditions(root, conditions);
|
|
|
|
if (guard != null) {
|
|
tests.push(guard);
|
|
} // <tests> ? <body> : <acc>
|
|
|
|
|
|
return {
|
|
type: 'ConditionalExpression',
|
|
test: (0, _Builders.conjunction)(tests),
|
|
consequent: body,
|
|
alternate: acc,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
}, lastBody);
|
|
} // There are bindings, so we produce an immediately invoked arrow expression.
|
|
// If the original argument is simple, no need for a new variable.
|
|
|
|
|
|
const statements = analyses.map(({
|
|
conditions,
|
|
bindings,
|
|
guard,
|
|
body
|
|
}) => {
|
|
const returnNode = {
|
|
type: 'ReturnStatement',
|
|
argument: body,
|
|
...(0, _Builders.etc)()
|
|
}; // If we have a guard, then we use a nested if statement
|
|
// `if (<guard>) return <body>`
|
|
|
|
const bodyNode = guard == null ? returnNode : {
|
|
type: 'IfStatement',
|
|
test: guard,
|
|
consequent: returnNode,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
const bindingNodes = statementsOfBindings(root, bindings);
|
|
const caseBody = bindingNodes.concat(bodyNode);
|
|
|
|
if (conditions.length > 0) {
|
|
const tests = testsOfConditions(root, conditions);
|
|
return {
|
|
type: 'IfStatement',
|
|
test: (0, _Builders.conjunction)(tests),
|
|
consequent: {
|
|
type: 'BlockStatement',
|
|
body: caseBody,
|
|
...(0, _Builders.etc)()
|
|
},
|
|
...(0, _Builders.etc)()
|
|
};
|
|
} else {
|
|
// No conditions, so no if statement
|
|
if (bindingNodes.length > 0) {
|
|
// Bindings require a block to introduce a new scope
|
|
return {
|
|
type: 'BlockStatement',
|
|
body: caseBody,
|
|
...(0, _Builders.etc)()
|
|
};
|
|
} else {
|
|
return bodyNode;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!hasWildcard) {
|
|
statements.push(fallthroughError((0, _astNodeMutationHelpers.shallowCloneNode)(root)));
|
|
}
|
|
|
|
const [params, args] = genRoot == null ? [[], []] : [[genRoot], [argument]]; // `((<params>) => { ... })(<args>)`, or
|
|
// `(() => { ... })()` if is simple argument.
|
|
|
|
return (0, _Builders.iife)(statements, params, args);
|
|
}
|
|
/**
|
|
* Match statement transform entry point.
|
|
*/
|
|
|
|
|
|
function mapMatchStatement(node) {
|
|
const {
|
|
argument,
|
|
cases
|
|
} = node;
|
|
const {
|
|
hasBindings,
|
|
hasWildcard,
|
|
analyses
|
|
} = analyzeCases(cases);
|
|
const topLabel = genIdent();
|
|
const isSimpleArgument = !hasBindings && calculateSimpleArgument(argument);
|
|
const genRoot = !isSimpleArgument ? genIdent() : null;
|
|
const root = genRoot == null ? argument : genRoot;
|
|
const statements = [];
|
|
|
|
if (genRoot != null) {
|
|
statements.push((0, _Builders.variableDeclaration)('const', genRoot, argument));
|
|
}
|
|
|
|
analyses.forEach(({
|
|
conditions,
|
|
bindings,
|
|
guard,
|
|
body
|
|
}) => {
|
|
const breakNode = {
|
|
type: 'BreakStatement',
|
|
label: (0, _astNodeMutationHelpers.shallowCloneNode)(topLabel),
|
|
...(0, _Builders.etc)()
|
|
};
|
|
const bodyStatements = body.body.concat(breakNode); // If we have a guard, then we use a nested if statement
|
|
// `if (<guard>) return <body>`
|
|
|
|
const guardedBodyStatements = guard == null ? bodyStatements : [{
|
|
type: 'IfStatement',
|
|
test: guard,
|
|
consequent: {
|
|
type: 'BlockStatement',
|
|
body: bodyStatements,
|
|
...(0, _Builders.etc)()
|
|
},
|
|
...(0, _Builders.etc)()
|
|
}];
|
|
const bindingNodes = statementsOfBindings(root, bindings);
|
|
const caseBody = bindingNodes.concat(guardedBodyStatements);
|
|
|
|
if (conditions.length > 0) {
|
|
const tests = testsOfConditions(root, conditions);
|
|
statements.push({
|
|
type: 'IfStatement',
|
|
test: (0, _Builders.conjunction)(tests),
|
|
consequent: {
|
|
type: 'BlockStatement',
|
|
body: caseBody,
|
|
...(0, _Builders.etc)()
|
|
},
|
|
...(0, _Builders.etc)()
|
|
});
|
|
} else {
|
|
// No conditions, so no if statement
|
|
statements.push({
|
|
type: 'BlockStatement',
|
|
body: caseBody,
|
|
...(0, _Builders.etc)()
|
|
});
|
|
}
|
|
});
|
|
|
|
if (!hasWildcard) {
|
|
statements.push(fallthroughError((0, _astNodeMutationHelpers.shallowCloneNode)(root)));
|
|
}
|
|
|
|
return {
|
|
type: 'LabeledStatement',
|
|
label: topLabel,
|
|
body: {
|
|
type: 'BlockStatement',
|
|
body: statements,
|
|
...(0, _Builders.etc)()
|
|
},
|
|
...(0, _Builders.etc)()
|
|
};
|
|
}
|
|
|
|
function transformProgram(program, _options) {
|
|
// Initialize so each file transformed starts freshly incrementing the
|
|
// variable name counter, and has its own usage tracking.
|
|
GenID = (0, _GenID.createGenID)('m');
|
|
return _SimpleTransform.SimpleTransform.transformProgram(program, {
|
|
transform(node) {
|
|
switch (node.type) {
|
|
case 'MatchExpression':
|
|
{
|
|
return mapMatchExpression(node);
|
|
}
|
|
|
|
case 'MatchStatement':
|
|
{
|
|
return mapMatchStatement(node);
|
|
}
|
|
|
|
case 'Identifier':
|
|
{
|
|
// A rudimentary check to avoid some collisions with our generated
|
|
// variable names. Ideally, we would have access a scope analyzer
|
|
// inside the transform instead.
|
|
if (GenID == null) {
|
|
throw Error('GenID must be initialized at the start of the transform.');
|
|
}
|
|
|
|
GenID.addUsage(node.name);
|
|
return node;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
});
|
|
} |