Skip to content

Commit

Permalink
Generate Hermes AST visitor keys from AST definitions
Browse files Browse the repository at this point in the history
Summary:
We will need to traverse the Hermes AST in order to convert it to a proper ESTree or Babel AST. Let's autogenerate the information needed to traverse the Hermes AST directly from the Hermes AST definitions similar to how this is done for the JS builder methods. We can use the C preprocessor to generate a map of the visitor keys in JS, mapping from the node's type to an object containing all of its node and node list children (string, boolean, and numeric fields are ignored). This allows us efficiently traverse the AST by looking up the visitor keys for each node when it is visited, and subsequently visiting the node child or all of the node list children.

Added autogeneration of these visitor keys to the `build.sh` script, being sure to format and then sign the generated file.

Also included a simple implementation of traversing the AST using these visitor keys in `HermesASTVisitor`. This is not yet used, but will be a foundation for the handful of conversions we need to do to modify the AST to be Babel or ESTree compliant.

Reviewed By: avp

Differential Revision: D23768640

fbshipit-source-id: 6ec34b7505075d3b3298c13f27f385c97e766d15
  • Loading branch information
Hans Halverson authored and facebook-github-bot committed Oct 1, 2020
1 parent d954c8f commit fe0e3dd
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 0 deletions.
9 changes: 9 additions & 0 deletions tools/hermes-parser/js/scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ else
WASM_PARSER="$1"
fi

# Use internal FB build or pass path to include path as second command line argument
FB_GET_INCLUDE_PATH="$THIS_DIR/facebook/getIncludePath.sh"
if [[ -f "$FB_GET_INCLUDE_PATH" ]]; then
INCLUDE_PATH=$("$FB_GET_INCLUDE_PATH")
else
INCLUDE_PATH="$2"
fi

node "$THIS_DIR/genWasmParser.js" "$WASM_PARSER"
node "$THIS_DIR/genVisitorKeys.js" "$INCLUDE_PATH"
61 changes: 61 additions & 0 deletions tools/hermes-parser/js/scripts/genVisitorKeys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) Facebook, Inc. and its 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';

const SignedSource = require('signedsource');

const fs = require('fs');
const path = require('path');

const {execSync} = require('child_process');

const OUTPUT_FILE = path.resolve(__dirname, '../build/HermesASTVisitorKeys.js');
const TEMPLATE_FILE = path.resolve(
__dirname,
'templates/HermesASTVisitorKeys.template',
);

// Create visitor keys file
const visitorKeys = execSync(
`cpp -P -I "${process.argv[2]}" "${TEMPLATE_FILE}"`,
);
const visitorKeysFileContents = `/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* This file was generated by tools/hermes-parser/js/scripts/build.sh
*
* ${SignedSource.getSigningToken()}
*
* @format
*/
'use strict';
const HERMES_AST_VISITOR_KEYS = {};
const NODE_CHILD = 'Node';
const NODE_LIST_CHILD = 'NodeList';
${visitorKeys}
module.exports = {
HERMES_AST_VISITOR_KEYS,
NODE_CHILD,
NODE_LIST_CHILD,
};
`;

// Format then sign file and write to disk
const formattedContents = execSync('prettier', {
input: visitorKeysFileContents,
}).toString();
fs.writeFileSync(OUTPUT_FILE, SignedSource.signFile(formattedContents));
216 changes: 216 additions & 0 deletions tools/hermes-parser/js/scripts/templates/HermesASTVisitorKeys.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// String, boolean, and number children are leaf nodes in AST and do not need to be visited
#define NodeLabel(ARG)
#define NodeBoolean(ARG)
#define NodeNumber(ARG)

// Mark node child as single child node that can be visited
#define NodePtr(ARG) ARG: NODE_CHILD,

// Mark node list children as multiple child nodes that can be visited
#define NodeList(ARG) ARG: NODE_LIST_CHILD,

#define ESTREE_NODE_0_ARGS( \
NAME, \
BASE) \
HERMES_AST_VISITOR_KEYS[#NAME] = {};

#define ESTREE_NODE_1_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
};

#define ESTREE_NODE_2_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
};

#define ESTREE_NODE_3_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
};

#define ESTREE_NODE_4_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT, \
ARG3TY, \
ARG3NM, \
ARG3PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
ARG3TY(ARG3NM) \
};

#define ESTREE_NODE_5_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT, \
ARG3TY, \
ARG3NM, \
ARG3PT, \
ARG4TY, \
ARG4NM, \
ARG4PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
ARG3TY(ARG3NM) \
ARG4TY(ARG4NM) \
};

#define ESTREE_NODE_6_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT, \
ARG3TY, \
ARG3NM, \
ARG3PT, \
ARG4TY, \
ARG4NM, \
ARG4PT, \
ARG5TY, \
ARG5NM, \
ARG5PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
ARG3TY(ARG3NM) \
ARG4TY(ARG4NM) \
ARG5TY(ARG5NM) \
};

#define ESTREE_NODE_7_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT, \
ARG3TY, \
ARG3NM, \
ARG3PT, \
ARG4TY, \
ARG4NM, \
ARG4PT, \
ARG5TY, \
ARG5NM, \
ARG5PT, \
ARG6TY, \
ARG6NM, \
ARG6PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
ARG3TY(ARG3NM) \
ARG4TY(ARG4NM) \
ARG5TY(ARG5NM) \
ARG6TY(ARG6NM) \
};

#define ESTREE_NODE_8_ARGS( \
NAME, \
BASE, \
ARG0TY, \
ARG0NM, \
ARG0OPT, \
ARG1TY, \
ARG1NM, \
ARG1PT, \
ARG2TY, \
ARG2NM, \
ARG2PT, \
ARG3TY, \
ARG3NM, \
ARG3PT, \
ARG4TY, \
ARG4NM, \
ARG4PT, \
ARG5TY, \
ARG5NM, \
ARG5PT, \
ARG6TY, \
ARG6NM, \
ARG6PT, \
ARG7TY, \
ARG7NM, \
ARG7PT) \
HERMES_AST_VISITOR_KEYS[#NAME] = { \
ARG0TY(ARG0NM) \
ARG1TY(ARG1NM) \
ARG2TY(ARG2NM) \
ARG3TY(ARG3NM) \
ARG4TY(ARG4NM) \
ARG5TY(ARG5NM) \
ARG6TY(ARG6NM) \
ARG7TY(ARG7NM) \
};

#include "hermes/AST/ESTree.def"
41 changes: 41 additions & 0 deletions tools/hermes-parser/js/src/HermesASTVisitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its 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';

const {
HERMES_AST_VISITOR_KEYS,
NODE_CHILD,
NODE_LIST_CHILD,
} = require('./HermesASTVisitorKeys');

function visitNode(node) {
if (node == null) {
return;
}

const visitorKeys = HERMES_AST_VISITOR_KEYS[node.type];
for (const key in visitorKeys) {
const childType = visitorKeys[key];
if (childType === NODE_CHILD) {
const child = node[key];
if (child != null) {
visitNode(node[key]);
}
} else if (childType === NODE_LIST_CHILD) {
for (const child of node[key]) {
if (child != null) {
visitNode(child);
}
}
}
}
}

module.exports = visitNode;

0 comments on commit fe0e3dd

Please sign in to comment.