Skip to content

Commit

Permalink
Add REPL features to Hermes CLI tool
Browse files Browse the repository at this point in the history
Reviewed By: avp

Differential Revision: D20996738

fbshipit-source-id: 5e7652737daf4db2681c23752e08748f8e9d75e6
  • Loading branch information
neildhar authored and facebook-github-bot committed Apr 15, 2020
1 parent f13566d commit db7344d
Show file tree
Hide file tree
Showing 6 changed files with 689 additions and 3 deletions.
15 changes: 15 additions & 0 deletions tools/hermes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

find_library(LIBREADLINE_FOUND readline)
find_library(LIBTINFO_FOUND tinfo)

if(LIBREADLINE_FOUND AND LIBTINFO_FOUND)
set(HAVE_LIBREADLINE 1)
set(LIBREADLINE ${LIBREADLINE_FOUND} ${LIBTINFO_FOUND})
endif()

configure_file(ReplConfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/ReplConfig.h
ESCAPE_QUOTES)

include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(HERMES_LINK_COMPONENTS LLVHSupport)

add_hermes_tool(hermes
hermes.cpp
repl.cpp
${ALL_HEADER_FILES}
)

Expand All @@ -25,6 +39,7 @@ target_link_libraries(hermes
dtoa
hermesInstrumentation
${CORE_FOUNDATION}
${LIBREADLINE}
)

hermes_link_icu(hermes)
19 changes: 19 additions & 0 deletions tools/hermes/ReplConfig.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 is processed by CMake.
// See https://cmake.org/cmake/help/v3.0/command/configure_file.html.

#ifndef REPLCONFIG_H
#define REPLCONFIG_H

/*
* These values are automatically set according to their cmake variables.
*/
#cmakedefine HAVE_LIBREADLINE 1

#endif // REPLCONFIG_H
242 changes: 242 additions & 0 deletions tools/hermes/evaluate-line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/**
* 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.
*/

/// Line evaluator for the REPL.
/// Runs a line of JS input and pretty-prints the output.
/// This is included into C++ file as a string literal at compilation time.
C_STRING((function() {
var colors = {};
function populateColors() {
colors.red = '\033[31m';
colors.green = '\033[32m';
colors.yellow = '\033[33m';
colors.blue = '\033[34m';
colors.magenta = '\033[35m';
colors.cyan = '\033[36m';
colors.white = '\033[37m';

colors.reset = '\033[0m';
}

function clearColors() {
colors.red = "";
colors.green = "";
colors.yellow = "";
colors.blue = "";
colors.magenta = "";
colors.cyan = "";
colors.white = "";

colors.reset = "";
}

function prettyPrintProp(value, prop, visited) {
var desc = Object.getOwnPropertyDescriptor(value, prop);
var result = "";
if (desc.enumerable) {
result += String(prop) + ': ';
} else {
result += '[' + String(prop) + ']: ';
}
if (desc.get || desc.set) {
result += colors.cyan + '[accessor]' + colors.reset;
} else {
result += prettyPrintRec(desc.value, visited);
}
return result;
}

function prettyPrintRec(value, visited) {
// First, check for cycles.
if (visited.has(value)) {
return colors.magenta + '[cyclic]' + colors.reset;
}

switch (typeof value) {
case "undefined":
return colors.white + "undefined" + colors.reset;
case "number":
return colors.yellow + String(value) + colors.reset;
case "string":
// Wrap strings in quotes so we their type.
return colors.green + '"' + value + '"' + colors.reset;
case "symbol":
return colors.green + String(value) + colors.reset;
case "boolean":
return colors.yellow + String(value) + colors.reset;
case "function":
// Default Function.prototype.toString doesn't look very good nested.
var functionColor = colors.cyan;
if (visited.size === 0) {
return functionColor + String(value) + colors.reset;
}
if (!value.name) {
return functionColor + '[Function]' + colors.reset;
}
return functionColor + '[Function ' + value.name + ']' + colors.reset;
}

if (value === null) {
return colors.white + 'null' + colors.reset;
}

// We know this is an object, so add it to the visited set.
visited.add(value);

if (Array.isArray(value)) {
// Print array using square brackets.
var length = value.length;
var elements = [];
var numEmpty = 0;
for (var i = 0; i < length; ++i) {
// First, handle the indexed properties of at most length.
if (!value.hasOwnProperty(i)) {
// No property here, just an empty slot.
++numEmpty;
} else {
if (numEmpty > 0) {
elements.push(
colors.white + numEmpty + ' x <empty>' + colors.reset);
}
numEmpty = 0;
if (value.propertyIsEnumerable(i)) {
elements.push(prettyPrintRec(value[i], visited));
} else {
elements.push(
'[' + String(i) + ']: ' + prettyPrintRec(value[i], visited));
}
}
}
if (numEmpty > 0) {
elements.push(
colors.white + numEmpty + ' x <empty>' + colors.reset);
}
var propNames = Object.getOwnPropertyNames(value);
for (var i = 0; i < propNames.length; ++i) {
// Handle other stored properties, and show their names.
var prop = propNames[i];
if (isNaN(Number(prop))) {
elements.push(prettyPrintProp(value, prop, visited));
}
}
return '[ ' + elements.join(', ') + ' ]';
}

if (value instanceof RegExp) {
return colors.green + value.toString() + colors.reset;
}

if (value instanceof Set) {
var elementStrings = [];
value.forEach(function(element) {
elementStrings.push(prettyPrintRec(element, visited));
});
return "Set { " + elementStrings.join(", ") + " }";
}

if (value instanceof Map) {
var elementStrings = [];
value.forEach(function(v, k) {
elementStrings.push(
prettyPrintRec(k, visited) + " => " + prettyPrintRec(v, visited)
);
});
return "Map { " + elementStrings.join(", ") + " }";
}

if (value instanceof Date) {
var isValid = !isNaN(value.getTime());
return colors.cyan + "[Date " +
(isValid ? value.toISOString() : "Invalid") +
"]" + colors.reset;
}

function isTypedArray(val) {
return val instanceof Int8Array ||
val instanceof Int16Array ||
val instanceof Int32Array ||
val instanceof Uint8Array ||
val instanceof Uint16Array ||
val instanceof Uint32Array ||
val instanceof Float32Array ||
val instanceof Float64Array;
}

if (isTypedArray(value)) {
var elementStrings = [];
value.forEach(function(i) {
elementStrings.push(prettyPrintRec(i, visited));
});
return value.constructor.name + " [ " + elementStrings.join(", ") + " ]";
}

// Regular object. Print out its properties directly as a literal.
var elements = [];
var propNames = Object.getOwnPropertyNames(value);
for (var i = 0; i < propNames.length; ++i) {
var prop = propNames[i];
elements.push(prettyPrintProp(value, prop, visited));
}
if (Object.getOwnPropertySymbols) {
var propSymbols = Object.getOwnPropertySymbols(value);
for (var i = 0; i < propSymbols.length; ++i) {
var prop = propSymbols[i];
elements.push(prettyPrintProp(value, prop, visited));
}
}
if (value.constructor && value.constructor.name && value.constructor.name !== "Object") {
return value.constructor.name + ' { ' + elements.join(', ') + ' }';
} else {
return '{ ' + elements.join(', ') + ' }';
}

}

function prettyPrint(value, isColored) {
isColored ? populateColors() : clearColors();
return prettyPrintRec(value, new Set());
}

var singleCommentPattern = new RegExp("^//");
var multiCommentPattern = new RegExp("^/\\*.*\\*/$");

/// Evaluates the specified line for the REPL.
/// Returns a pretty-printed string of the result,
/// and undefined if the input is empty or just a comment.
function evaluateLine(input, isColored) {
var output;
var trimmed = input.trim();
if (trimmed.length === 0) {
// Input is empty, return early.
return undefined;
}
if (singleCommentPattern.test(trimmed) ||
multiCommentPattern.test(trimmed)) {
// Input consists only of a comment, return early.
return undefined;
}
// Use (1, eval) to run indirect eval (global eval) and allow
// var declaration.
if (trimmed[0] === '{' && trimmed[trimmed.length - 1] === '}') {
try {
// The input starts with { and ends with }, so try wrap with ( and ).
output = (1, eval)('(' + input + ')');
} catch (e) {
// Wrapping the input failed, so just fall back to regular eval.
output = (1, eval)(input);
}
} else {
// Can't be mistaken for a block, so just use regular eval.
output = (1, eval)(input);
}

// Otherwise, just run eval directly.
return prettyPrint(output, isColored);
}

return evaluateLine;
})())
26 changes: 23 additions & 3 deletions tools/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "llvm/Support/SHA1.h"
#include "llvm/Support/Signals.h"

#include "repl.h"

using namespace hermes;

namespace cl {
Expand Down Expand Up @@ -166,6 +168,26 @@ static int executeHBCBytecodeFromCL(
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

static vm::RuntimeConfig getReplRuntimeConfig() {
return vm::RuntimeConfig::Builder()
.withGCConfig(
vm::GCConfig::Builder()
.withInitHeapSize(cl::InitHeapSize.bytes)
.withMaxHeapSize(cl::MaxHeapSize.bytes)
.withSanitizeConfig(vm::GCSanitizeConfig::Builder()
.withSanitizeRate(cl::GCSanitizeRate)
.withRandomSeed(cl::GCSanitizeRandomSeed)
.build())
.withShouldRecordStats(cl::GCPrintStats)
.build())
.withES6Proxy(cl::ES6Proxy)
.withES6Symbol(cl::ES6Symbol)
.withEnableHermesInternal(true)
.withEnableHermesInternalTestMethods(true)
.withAllowFunctionToStringWithRuntimeSource(cl::AllowFunctionToString)
.build();
}

int main(int argc, char **argv) {
// Normalize the arg vector.
llvm::InitLLVM initLLVM(argc, argv);
Expand All @@ -179,9 +201,7 @@ int main(int argc, char **argv) {
llvm::cl::ParseCommandLineOptions(argc, argv, "Hermes driver\n");

if (cl::InputFilenames.size() == 0) {
llvm::errs()
<< "Please provide a filename or '-' to take input from stdin\n";
return 0;
return repl(getReplRuntimeConfig());
}

// Tell compiler to emit async break check if time-limit feature is enabled
Expand Down
Loading

0 comments on commit db7344d

Please sign in to comment.