v8/src/d8.cc
patrick@chromium.org 1c0ebb7e1c Update d8 preemption mode to support multiple files per line. Each line is run
in its own context.  Files are separated by spaces, and will be run in order,
left to right.

Also add support for comment lines, which start with '#'.
Review URL: http://codereview.chromium.org/20319

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1306 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2009-02-19 04:26:04 +00:00

615 lines
19 KiB
C++

// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include "d8.h"
#include "d8-debug.h"
#include "debug.h"
#include "api.h"
#include "natives.h"
#include "platform.h"
namespace v8 {
const char* Shell::kHistoryFileName = ".d8_history";
const char* Shell::kPrompt = "d8> ";
LineEditor *LineEditor::first_ = NULL;
LineEditor::LineEditor(Type type, const char* name)
: type_(type),
name_(name),
next_(first_) {
first_ = this;
}
LineEditor* LineEditor::Get() {
LineEditor* current = first_;
LineEditor* best = current;
while (current != NULL) {
if (current->type_ > best->type_)
best = current;
current = current->next_;
}
return best;
}
class DumbLineEditor: public LineEditor {
public:
DumbLineEditor() : LineEditor(LineEditor::DUMB, "dumb") { }
virtual i::SmartPointer<char> Prompt(const char* prompt);
};
static DumbLineEditor dumb_line_editor;
i::SmartPointer<char> DumbLineEditor::Prompt(const char* prompt) {
static const int kBufferSize = 256;
char buffer[kBufferSize];
printf("%s", prompt);
char* str = fgets(buffer, kBufferSize, stdin);
return i::SmartPointer<char>(str ? i::StrDup(str) : str);
}
Shell::CounterMap Shell::counter_map_;
i::OS::MemoryMappedFile* Shell::counters_file_ = NULL;
CounterCollection Shell::local_counters_;
CounterCollection* Shell::counters_ = &local_counters_;
Persistent<Context> Shell::utility_context_;
Persistent<Context> Shell::evaluation_context_;
// Converts a V8 value to a C string.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}
// Executes a string within the current v8 context.
bool Shell::ExecuteString(Handle<String> source,
Handle<Value> name,
bool print_result,
bool report_exceptions) {
HandleScope handle_scope;
TryCatch try_catch;
if (i::FLAG_debugger) {
// When debugging make exceptions appear to be uncaught.
try_catch.SetVerbose(true);
}
Handle<Script> script = Script::Compile(source, name);
if (script.IsEmpty()) {
// Print errors that happened during compilation.
if (report_exceptions && !i::FLAG_debugger)
ReportException(&try_catch);
return false;
} else {
Handle<Value> result = script->Run();
if (result.IsEmpty()) {
// Print errors that happened during execution.
if (report_exceptions && !i::FLAG_debugger)
ReportException(&try_catch);
return false;
} else {
if (print_result && !result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(result);
const char* cstr = ToCString(str);
printf("%s\n", cstr);
}
return true;
}
}
}
Handle<Value> Shell::Print(const Arguments& args) {
bool first = true;
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope;
if (first) {
first = false;
} else {
printf(" ");
}
v8::String::Utf8Value str(args[i]);
const char* cstr = ToCString(str);
printf("%s", cstr);
}
printf("\n");
return Undefined();
}
Handle<Value> Shell::Load(const Arguments& args) {
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope;
String::Utf8Value file(args[i]);
if (*file == NULL) {
return ThrowException(String::New("Error loading file"));
}
Handle<String> source = ReadFile(*file);
if (source.IsEmpty()) {
return ThrowException(String::New("Error loading file"));
}
if (!ExecuteString(source, String::New(*file), false, false)) {
return ThrowException(String::New("Error executing file"));
}
}
return Undefined();
}
Handle<Value> Shell::Quit(const Arguments& args) {
int exit_code = args[0]->Int32Value();
OnExit();
exit(exit_code);
return Undefined();
}
Handle<Value> Shell::Version(const Arguments& args) {
return String::New(V8::GetVersion());
}
void Shell::ReportException(v8::TryCatch* try_catch) {
HandleScope handle_scope;
v8::String::Utf8Value exception(try_catch->Exception());
const char* exception_string = ToCString(exception);
Handle<Message> message = try_catch->Message();
if (message.IsEmpty()) {
// V8 didn't provide any extra information about this error; just
// print the exception.
printf("%s\n", exception_string);
} else {
// Print (filename):(line number): (message).
v8::String::Utf8Value filename(message->GetScriptResourceName());
const char* filename_string = ToCString(filename);
int linenum = message->GetLineNumber();
printf("%s:%i: %s\n", filename_string, linenum, exception_string);
// Print line of source code.
v8::String::Utf8Value sourceline(message->GetSourceLine());
const char* sourceline_string = ToCString(sourceline);
printf("%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
int start = message->GetStartColumn();
for (int i = 0; i < start; i++) {
printf(" ");
}
int end = message->GetEndColumn();
for (int i = start; i < end; i++) {
printf("^");
}
printf("\n");
}
}
Handle<Array> Shell::GetCompletions(Handle<String> text, Handle<String> full) {
HandleScope handle_scope;
Context::Scope context_scope(utility_context_);
Handle<Object> global = utility_context_->Global();
Handle<Value> fun = global->Get(String::New("GetCompletions"));
static const int kArgc = 3;
Handle<Value> argv[kArgc] = { evaluation_context_->Global(), text, full };
Handle<Value> val = Handle<Function>::Cast(fun)->Call(global, kArgc, argv);
return handle_scope.Close(Handle<Array>::Cast(val));
}
Handle<String> Shell::DebugEventToText(Handle<Object> event) {
HandleScope handle_scope;
Context::Scope context_scope(utility_context_);
Handle<Object> global = utility_context_->Global();
Handle<Value> fun = global->Get(String::New("DebugEventToText"));
TryCatch try_catch;
try_catch.SetVerbose(true);
static const int kArgc = 1;
Handle<Value> argv[kArgc] = { event };
Handle<Value> val = Handle<Function>::Cast(fun)->Call(global, kArgc, argv);
if (try_catch.HasCaught()) {
return handle_scope.Close(try_catch.Exception()->ToString());
} else {
return handle_scope.Close(Handle<String>::Cast(val));
}
}
Handle<Value> Shell::DebugCommandToJSONRequest(Handle<String> command) {
Context::Scope context_scope(utility_context_);
Handle<Object> global = utility_context_->Global();
Handle<Value> fun = global->Get(String::New("DebugCommandToJSONRequest"));
static const int kArgc = 1;
Handle<Value> argv[kArgc] = { command };
Handle<Value> val = Handle<Function>::Cast(fun)->Call(global, kArgc, argv);
return val;
}
Handle<Object> Shell::DebugResponseDetails(Handle<String> response) {
Context::Scope context_scope(utility_context_);
Handle<Object> global = utility_context_->Global();
Handle<Value> fun = global->Get(String::New("DebugResponseDetails"));
static const int kArgc = 1;
Handle<Value> argv[kArgc] = { response };
Handle<Value> val = Handle<Function>::Cast(fun)->Call(global, kArgc, argv);
return Handle<Object>::Cast(val);
}
int32_t* Counter::Bind(const char* name) {
int i;
for (i = 0; i < kMaxNameSize - 1 && name[i]; i++)
name_[i] = static_cast<char>(name[i]);
name_[i] = '\0';
return &counter_;
}
CounterCollection::CounterCollection() {
magic_number_ = 0xDEADFACE;
max_counters_ = kMaxCounters;
max_name_size_ = Counter::kMaxNameSize;
counters_in_use_ = 0;
}
Counter* CounterCollection::GetNextCounter() {
if (counters_in_use_ == kMaxCounters) return NULL;
return &counters_[counters_in_use_++];
}
void Shell::MapCounters(const char* name) {
counters_file_ = i::OS::MemoryMappedFile::create(name,
sizeof(CounterCollection), &local_counters_);
void* memory = (counters_file_ == NULL) ?
NULL : counters_file_->memory();
if (memory == NULL) {
printf("Could not map counters file %s\n", name);
exit(1);
}
counters_ = static_cast<CounterCollection*>(memory);
V8::SetCounterFunction(LookupCounter);
}
int* Shell::LookupCounter(const char* name) {
CounterMap::iterator item = counter_map_.find(name);
if (item != counter_map_.end()) {
Counter* result = (*item).second;
return result->ptr();
}
Counter* result = counters_->GetNextCounter();
if (result == NULL) return NULL;
counter_map_[name] = result;
return result->Bind(name);
}
void Shell::Initialize() {
// Set up counters
if (i::FLAG_map_counters != NULL)
MapCounters(i::FLAG_map_counters);
if (i::FLAG_dump_counters)
V8::SetCounterFunction(LookupCounter);
// Initialize the global objects
HandleScope scope;
Handle<ObjectTemplate> global_template = ObjectTemplate::New();
global_template->Set(String::New("print"), FunctionTemplate::New(Print));
global_template->Set(String::New("load"), FunctionTemplate::New(Load));
global_template->Set(String::New("quit"), FunctionTemplate::New(Quit));
global_template->Set(String::New("version"), FunctionTemplate::New(Version));
utility_context_ = Context::New(NULL, global_template);
utility_context_->SetSecurityToken(Undefined());
Context::Scope utility_scope(utility_context_);
i::JSArguments js_args = i::FLAG_js_arguments;
i::Handle<i::FixedArray> arguments_array =
i::Factory::NewFixedArray(js_args.argc());
for (int j = 0; j < js_args.argc(); j++) {
i::Handle<i::String> arg =
i::Factory::NewStringFromUtf8(i::CStrVector(js_args[j]));
arguments_array->set(j, *arg);
}
i::Handle<i::JSArray> arguments_jsarray =
i::Factory::NewJSArrayWithElements(arguments_array);
global_template->Set(String::New("arguments"),
Utils::ToLocal(arguments_jsarray));
// Install the debugger object in the utility scope
i::Debug::Load();
i::JSObject* debug = i::Debug::debug_context()->global();
utility_context_->Global()->Set(String::New("$debug"),
Utils::ToLocal(&debug));
// Run the d8 shell utility script in the utility context
int source_index = i::NativesCollection<i::D8>::GetIndex("d8");
i::Vector<const char> shell_source
= i::NativesCollection<i::D8>::GetScriptSource(source_index);
i::Vector<const char> shell_source_name
= i::NativesCollection<i::D8>::GetScriptName(source_index);
Handle<String> source = String::New(shell_source.start(),
shell_source.length());
Handle<String> name = String::New(shell_source_name.start(),
shell_source_name.length());
Script::Compile(source, name)->Run();
// Create the evaluation context
evaluation_context_ = Context::New(NULL, global_template);
evaluation_context_->SetSecurityToken(Undefined());
// Set the security token of the debug context to allow access.
i::Debug::debug_context()->set_security_token(i::Heap::undefined_value());
}
void Shell::OnExit() {
if (i::FLAG_dump_counters) {
::printf("+----------------------------------------+-------------+\n");
::printf("| Name | Value |\n");
::printf("+----------------------------------------+-------------+\n");
for (CounterMap::iterator i = counter_map_.begin();
i != counter_map_.end();
i++) {
Counter* counter = (*i).second;
::printf("| %-38s | %11i |\n", (*i).first, counter->value());
}
::printf("+----------------------------------------+-------------+\n");
}
if (counters_file_ != NULL)
delete counters_file_;
}
static char* ReadChars(const char *name, int* size_out) {
v8::Unlocker unlocker; // Release the V8 lock while reading files.
FILE* file = i::OS::FOpen(name, "rb");
if (file == NULL) return NULL;
fseek(file, 0, SEEK_END);
int size = ftell(file);
rewind(file);
char* chars = new char[size + 1];
chars[size] = '\0';
for (int i = 0; i < size;) {
int read = fread(&chars[i], 1, size - i, file);
i += read;
}
fclose(file);
*size_out = size;
return chars;
}
static char* ReadToken(const char* data, char token) {
char* next = ::strchr(data, token);
if (next != NULL) {
*next = '\0';
return (next + 1);
}
return NULL;
}
static char* ReadLine(const char* data) {
return ReadToken(data, '\n');
}
static char* ReadWord(const char* data) {
return ReadToken(data, ' ');
}
// Reads a file into a v8 string.
Handle<String> Shell::ReadFile(const char* name) {
int size = 0;
char* chars = ReadChars(name, &size);
if (chars == NULL) return Handle<String>();
Handle<String> result = String::New(chars);
delete[] chars;
return result;
}
void Shell::RunShell() {
LineEditor* editor = LineEditor::Get();
printf("V8 version %s [console: %s]\n", V8::GetVersion(), editor->name());
editor->Open();
while (true) {
Locker locker;
HandleScope handle_scope;
Context::Scope context_scope(evaluation_context_);
i::SmartPointer<char> input = editor->Prompt(Shell::kPrompt);
if (input.is_empty())
break;
editor->AddHistory(*input);
Handle<String> name = String::New("(d8)");
ExecuteString(String::New(*input), name, true, true);
}
editor->Close();
printf("\n");
}
class ShellThread : public i::Thread {
public:
ShellThread(int no, i::Vector<const char> files)
: no_(no), files_(files) { }
virtual void Run();
private:
int no_;
i::Vector<const char> files_;
};
void ShellThread::Run() {
// Prepare the context for this thread.
Locker locker;
HandleScope scope;
Handle<ObjectTemplate> global_template = ObjectTemplate::New();
global_template->Set(String::New("print"),
FunctionTemplate::New(Shell::Print));
global_template->Set(String::New("load"),
FunctionTemplate::New(Shell::Load));
global_template->Set(String::New("version"),
FunctionTemplate::New(Shell::Version));
char* ptr = const_cast<char*>(files_.start());
while ((ptr != NULL) && (*ptr != '\0')) {
// For each newline-separated line.
char* next_line = ReadLine(ptr);
if (*ptr == '#') {
// Skip comment lines.
ptr = next_line;
continue;
}
Persistent<Context> thread_context = Context::New(NULL, global_template);
thread_context->SetSecurityToken(Undefined());
Context::Scope context_scope(thread_context);
while ((ptr != NULL) && (*ptr != '\0')) {
char* filename = ptr;
ptr = ReadWord(ptr);
// Skip empty strings.
if (strlen(filename) == 0) {
break;
}
Handle<String> str = Shell::ReadFile(filename);
if (str.IsEmpty()) {
printf("WARNING: %s not found\n", filename);
break;
}
Shell::ExecuteString(str, String::New(filename), false, false);
}
thread_context.Dispose();
ptr = next_line;
}
}
int Shell::Main(int argc, char* argv[]) {
i::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
if (i::FLAG_help) {
return 1;
}
Initialize();
bool run_shell = (argc == 1);
i::List<i::Thread*> threads(1);
{
// Acquire the V8 lock once initialization has finished. Since the thread
// below may spawn new threads accessing V8 holding the V8 lock here is
// mandatory.
Locker locker;
Context::Scope context_scope(evaluation_context_);
for (int i = 1; i < argc; i++) {
char* str = argv[i];
if (strcmp(str, "--shell") == 0) {
run_shell = true;
} else if (strcmp(str, "-f") == 0) {
// Ignore any -f flags for compatibility with other stand-alone
// JavaScript engines.
continue;
} else if (strncmp(str, "--", 2) == 0) {
printf("Warning: unknown flag %s.\nTry --help for options\n", str);
} else if (strcmp(str, "-e") == 0 && i + 1 < argc) {
// Execute argument given to -e option directly.
v8::HandleScope handle_scope;
v8::Handle<v8::String> file_name = v8::String::New("unnamed");
v8::Handle<v8::String> source = v8::String::New(argv[i + 1]);
if (!ExecuteString(source, file_name, false, true))
return 1;
i++;
} else if (strcmp(str, "-p") == 0 && i + 1 < argc) {
// Use the lowest possible thread preemption interval to test as many
// edgecases as possible.
Locker::StartPreemption(1);
int size = 0;
const char *files = ReadChars(argv[++i], &size);
if (files == NULL) return 1;
ShellThread *thread =
new ShellThread(threads.length(),
i::Vector<const char>(files, size));
thread->Start();
threads.Add(thread);
} else {
// Use all other arguments as names of files to load and run.
HandleScope handle_scope;
Handle<String> file_name = v8::String::New(str);
Handle<String> source = ReadFile(str);
if (source.IsEmpty()) {
printf("Error reading '%s'\n", str);
return 1;
}
if (!ExecuteString(source, file_name, false, true))
return 1;
}
}
if (i::FLAG_debugger)
v8::Debug::SetDebugEventListener(HandleDebugEvent);
}
if (run_shell)
RunShell();
for (int i = 0; i < threads.length(); i++) {
i::Thread *thread = threads[i];
thread->Join();
delete thread;
}
OnExit();
return 0;
}
} // namespace v8
int main(int argc, char* argv[]) {
return v8::Shell::Main(argc, argv);
}