Chromium Code Reviews| Index: src/d8.cc |
| diff --git a/src/d8.cc b/src/d8.cc |
| index 5f57350093943821ac9157cb8cf2bca452ecc236..10347997594340e313fcb5a833ac7ca407670c96 100644 |
| --- a/src/d8.cc |
| +++ b/src/d8.cc |
| @@ -26,33 +26,49 @@ |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| +#if (defined(USING_V8_SHARED) || defined(V8_SHARED)) |
| +#define D8_LIGHT |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
How about renaming D8_LIGHT to D8_WITH_SHARED?
It
|
| +#endif |
| + |
| #ifdef COMPRESS_STARTUP_DATA_BZ2 |
| #include <bzlib.h> |
| #endif |
| + |
| #include <errno.h> |
| #include <stdlib.h> |
| +#include <string.h> |
| -#include "v8.h" |
| +#ifdef D8_LIGHT |
| +#include <assert.h> |
| +#include "../include/v8-testing.h" |
| +#endif // D8_LIGHT |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
Two spaces before //. Also in other places below.
|
| #include "d8.h" |
| + |
| +#ifndef D8_LIGHT |
| +#include "api.h" |
| +#include "checks.h" |
| #include "d8-debug.h" |
| #include "debug.h" |
| -#include "api.h" |
| #include "natives.h" |
| #include "platform.h" |
| +#include "v8.h" |
| +#endif // D8_LIGHT |
| #if !defined(_WIN32) && !defined(_WIN64) |
| #include <unistd.h> // NOLINT |
| #endif |
| -namespace v8 { |
| - |
| +#ifdef D8_LIGHT |
| +#define ASSERT(condition) assert(condition) |
| +#endif // D8_LIGHT |
| -const char* Shell::kHistoryFileName = ".d8_history"; |
| -const char* Shell::kPrompt = "d8> "; |
| +namespace v8 { |
| +#ifndef D8_LIGHT |
| LineEditor *LineEditor::first_ = NULL; |
| +const char* Shell::kHistoryFileName = ".d8_history"; |
| LineEditor::LineEditor(Type type, const char* name) |
| @@ -98,17 +114,22 @@ CounterMap* Shell::counter_map_; |
| i::OS::MemoryMappedFile* Shell::counters_file_ = NULL; |
| CounterCollection Shell::local_counters_; |
| CounterCollection* Shell::counters_ = &local_counters_; |
| +i::Mutex* Shell::context_mutex_(i::OS::CreateMutex()); |
| Persistent<Context> Shell::utility_context_; |
| +#endif // D8_LIGHT |
| + |
| Persistent<Context> Shell::evaluation_context_; |
| -i::Mutex* Shell::context_mutex_(i::OS::CreateMutex()); |
| ShellOptions Shell::options; |
| +const char* Shell::kPrompt = "d8> "; |
| +#ifndef D8_LIGHT |
| bool CounterMap::Match(void* key1, void* key2) { |
| const char* name1 = reinterpret_cast<const char*>(key1); |
| const char* name2 = reinterpret_cast<const char*>(key2); |
| return strcmp(name1, name2) == 0; |
| } |
| +#endif // D8_LIGHT |
| // Converts a V8 value to a C string. |
| @@ -122,17 +143,22 @@ bool Shell::ExecuteString(Handle<String> source, |
| Handle<Value> name, |
| bool print_result, |
| bool report_exceptions) { |
| +#ifndef D8_LIGHT |
| + bool FLAG_debugger = i::FLAG_debugger; |
| +#else |
| + bool FLAG_debugger = false; |
| +#endif // D8_LIGHT |
| HandleScope handle_scope; |
| TryCatch try_catch; |
| options.script_executed = true; |
| - if (i::FLAG_debugger) { |
| + if (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) |
| + if (report_exceptions && !FLAG_debugger) |
| ReportException(&try_catch); |
| return false; |
| } else { |
| @@ -140,7 +166,7 @@ bool Shell::ExecuteString(Handle<String> source, |
| if (result.IsEmpty()) { |
| ASSERT(try_catch.HasCaught()); |
| // Print errors that happened during execution. |
| - if (report_exceptions && !i::FLAG_debugger) |
| + if (report_exceptions && !FLAG_debugger) |
| ReportException(&try_catch); |
| return false; |
| } else { |
| @@ -196,15 +222,11 @@ Handle<Value> Shell::Read(const Arguments& args) { |
| Handle<Value> Shell::ReadLine(const Arguments& args) { |
| - i::SmartPointer<char> line(i::ReadLine("")); |
| - if (*line == NULL) { |
| - return Null(); |
| - } |
| - size_t len = strlen(*line); |
| - if (len > 0 && line[len - 1] == '\n') { |
| - --len; |
| - } |
| - return String::New(*line, len); |
| + static const int kBufferSize = 256; |
| + char buffer[kBufferSize]; |
| + if (fgets(buffer, kBufferSize, stdin) == NULL) return Null(); |
| + buffer[strlen(buffer) - 1] = '\0'; // chomp |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
Check for '\' before overwriting with '\0'?
Two s
|
| + return String::New(buffer); |
| } |
| @@ -236,6 +258,7 @@ Handle<Value> Shell::CreateExternalArray(const Arguments& args, |
| return ThrowException( |
| String::New("Array constructor needs one parameter.")); |
| } |
| + static const int kMaxLength = 0x3fffffff; |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
Maybe assert that this is equal to i::ExternalArr
|
| size_t length = 0; |
| if (args[0]->IsUint32()) { |
| length = args[0]->Uint32Value(); |
| @@ -244,7 +267,7 @@ Handle<Value> Shell::CreateExternalArray(const Arguments& args, |
| if (raw_length < 0) { |
| return ThrowException(String::New("Array length must not be negative.")); |
| } |
| - if (raw_length > i::ExternalArray::kMaxLength) { |
| + if (raw_length > kMaxLength) { |
| return ThrowException( |
| String::New("Array length exceeds maximum length.")); |
| } |
| @@ -252,7 +275,7 @@ Handle<Value> Shell::CreateExternalArray(const Arguments& args, |
| } else { |
| return ThrowException(String::New("Array length must be a number.")); |
| } |
| - if (length > static_cast<size_t>(i::ExternalArray::kMaxLength)) { |
| + if (length > static_cast<size_t>(kMaxLength)) { |
| return ThrowException(String::New("Array length exceeds maximum length.")); |
| } |
| void* data = calloc(length, element_size); |
| @@ -332,7 +355,9 @@ Handle<Value> Shell::Yield(const Arguments& args) { |
| Handle<Value> Shell::Quit(const Arguments& args) { |
| int exit_code = args[0]->Int32Value(); |
| +#ifndef D8_LIGHT |
| OnExit(); |
| +#endif // D8_LIGHT |
| exit(exit_code); |
| return Undefined(); |
| } |
| @@ -381,6 +406,7 @@ void Shell::ReportException(v8::TryCatch* try_catch) { |
| } |
| +#ifndef D8_LIGHT |
| Handle<Array> Shell::GetCompletions(Handle<String> text, Handle<String> full) { |
| HandleScope handle_scope; |
| Context::Scope context_scope(utility_context_); |
| @@ -414,9 +440,11 @@ Handle<Value> Shell::DebugCommandToJSONRequest(Handle<String> command) { |
| Handle<Value> val = Handle<Function>::Cast(fun)->Call(global, kArgc, argv); |
| return val; |
| } |
| -#endif |
| +#endif // ENABLE_DEBUGGER_SUPPORT |
| +#endif // D8_LIGHT |
| +#ifndef D8_LIGHT |
| int32_t* Counter::Bind(const char* name, bool is_histogram) { |
| int i; |
| for (i = 0; i < kMaxNameSize - 1 && name[i]; i++) |
| @@ -449,7 +477,7 @@ Counter* CounterCollection::GetNextCounter() { |
| void Shell::MapCounters(const char* name) { |
| counters_file_ = i::OS::MemoryMappedFile::create(name, |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
While at it consider moving name to the line with
|
| - sizeof(CounterCollection), &local_counters_); |
| + sizeof(CounterCollection), &local_counters_); |
| void* memory = (counters_file_ == NULL) ? |
| NULL : counters_file_->memory(); |
| if (memory == NULL) { |
| @@ -514,6 +542,7 @@ void Shell::AddHistogramSample(void* histogram, int sample) { |
| counter->AddSample(sample); |
| } |
| + |
| void Shell::InstallUtilityScript() { |
| Locker lock; |
| HandleScope scope; |
| @@ -532,7 +561,7 @@ void Shell::InstallUtilityScript() { |
| utility_context_->Global()->Set(String::New("$debug"), |
| Utils::ToLocal(js_debug)); |
| debug->debug_context()->set_security_token(HEAP->undefined_value()); |
| -#endif |
| +#endif // ENABLE_DEBUGGER_SUPPORT |
| // Run the d8 shell utility script in the utility context |
| int source_index = i::NativesCollection<i::D8>::GetIndex("d8"); |
| @@ -550,10 +579,10 @@ void Shell::InstallUtilityScript() { |
| // in the debugger. |
| i::Handle<i::Object> compiled_script = Utils::OpenHandle(*script); |
| i::Handle<i::Script> script_object = compiled_script->IsJSFunction() |
| - ? i::Handle<i::Script>(i::Script::cast( |
| - i::JSFunction::cast(*compiled_script)->shared()->script())) |
| - : i::Handle<i::Script>(i::Script::cast( |
| - i::SharedFunctionInfo::cast(*compiled_script)->script())); |
| + ? i::Handle<i::Script>(i::Script::cast( |
| + i::JSFunction::cast(*compiled_script)->shared()->script())) |
| + : i::Handle<i::Script>(i::Script::cast( |
| + i::SharedFunctionInfo::cast(*compiled_script)->script())); |
| script_object->set_type(i::Smi::FromInt(i::Script::TYPE_NATIVE)); |
| #ifdef ENABLE_DEBUGGER_SUPPORT |
| @@ -561,8 +590,9 @@ void Shell::InstallUtilityScript() { |
| if (i::FLAG_debugger && !i::FLAG_debugger_agent) { |
| v8::Debug::SetDebugEventListener(HandleDebugEvent); |
| } |
| -#endif |
| +#endif // ENABLE_DEBUGGER_SUPPORT |
| } |
| +#endif // D8_LIGHT |
| #ifdef COMPRESS_STARTUP_DATA_BZ2 |
| @@ -629,9 +659,11 @@ Handle<ObjectTemplate> Shell::CreateGlobalTemplate() { |
| global_template->Set(String::New("lol_is_enabled"), Boolean::New(false)); |
| #endif |
| +#ifndef D8_LIGHT |
| Handle<ObjectTemplate> os_templ = ObjectTemplate::New(); |
| AddOSMethods(os_templ); |
| global_template->Set(String::New("os"), os_templ); |
| +#endif // D8_LIGHT |
| return global_template; |
| } |
| @@ -647,6 +679,7 @@ void Shell::Initialize() { |
| } |
| #endif |
| +#ifndef D8_LIGHT |
| Shell::counter_map_ = new CounterMap(); |
| // Set up counters |
| if (i::StrLength(i::FLAG_map_counters) != 0) |
| @@ -656,9 +689,10 @@ void Shell::Initialize() { |
| V8::SetCreateHistogramFunction(CreateHistogram); |
| V8::SetAddHistogramSampleFunction(AddHistogramSample); |
| } |
| - |
| +#endif // D8_LIGHT |
| if (options.test_shell) return; |
| +#ifndef D8_LIGHT |
| Locker lock; |
| HandleScope scope; |
| Handle<ObjectTemplate> global_template = CreateGlobalTemplate(); |
| @@ -669,18 +703,22 @@ void Shell::Initialize() { |
| if (i::FLAG_debugger_agent) { |
| v8::Debug::EnableAgent("d8 shell", i::FLAG_debugger_port, true); |
| } |
| -#endif |
| +#endif // ENABLE_DEBUGGER_SUPPORT |
| +#endif // D8_LIGHT |
| } |
| Persistent<Context> Shell::CreateEvaluationContext() { |
| +#ifndef D8_LIGHT |
| // This needs to be a critical section since this is not thread-safe |
| i::ScopedLock lock(context_mutex_); |
| +#endif // D8_LIGHT |
| // Initialize the global objects |
| Handle<ObjectTemplate> global_template = CreateGlobalTemplate(); |
| Persistent<Context> context = Context::New(NULL, global_template); |
| Context::Scope scope(context); |
| +#ifndef D8_LIGHT |
| i::JSArguments js_args = i::FLAG_js_arguments; |
| i::Handle<i::FixedArray> arguments_array = |
| FACTORY->NewFixedArray(js_args.argc()); |
| @@ -692,11 +730,13 @@ Persistent<Context> Shell::CreateEvaluationContext() { |
| i::Handle<i::JSArray> arguments_jsarray = |
| FACTORY->NewJSArrayWithElements(arguments_array); |
| context->Global()->Set(String::New("arguments"), |
| - Utils::ToLocal(arguments_jsarray)); |
| + Utils::ToLocal(arguments_jsarray)); |
| +#endif // D8_LIGHT |
| return context; |
| } |
| +#ifndef D8_LIGHT |
| void Shell::OnExit() { |
| if (i::FLAG_dump_counters) { |
| printf("+----------------------------------------+-------------+\n"); |
| @@ -716,12 +756,17 @@ void Shell::OnExit() { |
| if (counters_file_ != NULL) |
| delete counters_file_; |
| } |
| +#endif // D8_LIGHT |
| static char* ReadChars(const char* name, int* size_out) { |
| // Release the V8 lock while reading files. |
| v8::Unlocker unlocker(Isolate::GetCurrent()); |
| +#ifndef D8_LIGHT |
| FILE* file = i::OS::FOpen(name, "rb"); |
| +#else |
| + FILE* file = fopen(name, "rb"); // TODO: reading from a directory hangs! |
|
Yang
2011/07/13 13:26:10
This is a known minor issue. I'll come with a solu
|
| +#endif // D8_LIGHT |
| if (file == NULL) return NULL; |
| fseek(file, 0, SEEK_END); |
| @@ -740,6 +785,7 @@ static char* ReadChars(const char* name, int* size_out) { |
| } |
| +#ifndef D8_LIGHT |
| static char* ReadToken(char* data, char token) { |
| char* next = i::OS::StrChr(data, token); |
| if (next != NULL) { |
| @@ -759,6 +805,7 @@ static char* ReadLine(char* data) { |
| static char* ReadWord(char* data) { |
| return ReadToken(data, ' '); |
| } |
| +#endif // D8_LIGHT |
| // Reads a file into a v8 string. |
| @@ -773,34 +820,44 @@ Handle<String> Shell::ReadFile(const char* name) { |
| void Shell::RunShell() { |
| + Locker locker; |
| + Context::Scope context_scope(evaluation_context_); |
| + HandleScope handle_scope; |
| + Handle<String> name = String::New("(d8)"); |
| +#ifndef D8_LIGHT |
| LineEditor* editor = LineEditor::Get(); |
| printf("V8 version %s [console: %s]\n", V8::GetVersion(), editor->name()); |
| if (i::FLAG_debugger) { |
| printf("JavaScript debugger enabled\n"); |
| } |
| - |
| 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; |
| + if (input.is_empty()) break; |
| editor->AddHistory(*input); |
| - Handle<String> name = String::New("(d8)"); |
| ExecuteString(String::New(*input), name, true, true); |
| } |
| editor->Close(); |
| +#else |
| + printf("V8 version %s [D8 light with shared library]\n", V8::GetVersion()); |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
with -> using
|
| + static const int kBufferSize = 256; |
| + while (true) { |
| + char buffer[kBufferSize]; |
| + printf("%s", Shell::kPrompt); |
| + if (fgets(buffer, kBufferSize, stdin) == NULL) break; |
| + ExecuteString(String::New(buffer), name, true, true); |
| + } |
| +#endif // D8_LIGHT |
| printf("\n"); |
| } |
| +#ifndef D8_LIGHT |
| class ShellThread : public i::Thread { |
| public: |
| ShellThread(int no, i::Vector<const char> files) |
| - : Thread("d8:ShellThread"), |
| - no_(no), files_(files) { } |
| + : Thread("d8:ShellThread"), |
| + no_(no), files_(files) { } |
| virtual void Run(); |
| private: |
| int no_; |
| @@ -848,6 +905,7 @@ void ShellThread::Run() { |
| ptr = next_line; |
| } |
| } |
| +#endif // D8_LIGHT |
| void SourceGroup::ExitShell(int exit_code) { |
| @@ -894,7 +952,11 @@ void SourceGroup::Execute() { |
| Handle<String> SourceGroup::ReadFile(const char* name) { |
| - FILE* file = fopen(name, "rb"); |
| +#ifndef D8_LIGHT |
| + FILE* file = i::OS::FOpen(name, "rb"); |
| +#else |
| + FILE* file = fopen(name, "rb"); // TODO: reading from a directory hangs! |
|
Yang
2011/07/13 13:26:10
Ditto.
|
| +#endif // D8_LIGHT |
| if (file == NULL) return Handle<String>(); |
| fseek(file, 0, SEEK_END); |
| @@ -914,6 +976,7 @@ Handle<String> SourceGroup::ReadFile(const char* name) { |
| } |
| +#ifndef D8_LIGHT |
| i::Thread::Options SourceGroup::GetThreadOptions() { |
| i::Thread::Options options; |
| options.name = "IsolateThread"; |
| @@ -965,6 +1028,7 @@ void SourceGroup::WaitForThread() { |
| done_semaphore_->Wait(); |
| } |
| } |
| +#endif // D8_LIGHT |
| bool Shell::SetOptions(int argc, char* argv[]) { |
| @@ -986,12 +1050,26 @@ bool Shell::SetOptions(int argc, char* argv[]) { |
| options.test_shell = true; |
| argv[i] = NULL; |
| } else if (strcmp(argv[i], "--preemption") == 0) { |
|
Yang
2011/07/13 13:26:10
Many options are not supported by the light versio
|
| +#ifdef D8_LIGHT |
| + printf("D8 with shared library does not support multi-threading\n"); |
| + return false; |
| +#else |
| options.use_preemption = true; |
| argv[i] = NULL; |
| +#endif // D8_LIGHT |
| } else if (strcmp(argv[i], "--no-preemption") == 0) { |
| +#ifdef D8_LIGHT |
| + printf("D8 with shared library does not support multi-threading\n"); |
| + return false; |
| +#else |
| options.use_preemption = false; |
| argv[i] = NULL; |
| +#endif // D8_LIGHT |
| } else if (strcmp(argv[i], "--preemption-interval") == 0) { |
| +#ifdef D8_LIGHT |
| + printf("D8 with shared library does not support multi-threading\n"); |
| + return false; |
| +#else |
| if (++i < argc) { |
| argv[i-1] = NULL; |
| char* end = NULL; |
| @@ -1007,15 +1085,33 @@ bool Shell::SetOptions(int argc, char* argv[]) { |
| printf("Missing value for --preemption-interval\n"); |
| return false; |
| } |
| +#endif // D8_LIGHT |
| } else if (strcmp(argv[i], "-f") == 0) { |
| // Ignore any -f flags for compatibility with other stand-alone |
| // JavaScript engines. |
| continue; |
| } else if (strcmp(argv[i], "--isolate") == 0) { |
| +#ifdef D8_LIGHT |
| + printf("D8 with shared library does not support multi-threading\n"); |
| + return false; |
| +#endif // D8_LIGHT |
| options.num_isolates++; |
| } |
| +#ifdef D8_LIGHT |
| + else if (strcmp(argv[i], "--dump-counters") == 0) { |
| + printf("D8 with shared library does not include counters\n"); |
| + return false; |
| + } else if (strcmp(argv[i], "-p") == 0) { |
| + printf("D8 with shared library does not support multi-threading\n"); |
| + return false; |
| + } else if (strcmp(argv[i], "--debugger") == 0) { |
| + printf("Javascript debugger not included\n"); |
| + return false; |
| + } |
| +#endif // D8_LIGHT |
| } |
| +#ifndef D8_LIGHT |
| // Run parallel threads if we are not using --isolate |
| for (int i = 1; i < argc; i++) { |
| if (argv[i] == NULL) continue; |
| @@ -1038,6 +1134,7 @@ bool Shell::SetOptions(int argc, char* argv[]) { |
| options.parallel_files->Add(i::Vector<const char>(files, size)); |
| } |
| } |
| +#endif // D8_LIGHT |
| v8::V8::SetFlagsFromCommandLine(&argc, argv, true); |
| @@ -1058,13 +1155,15 @@ bool Shell::SetOptions(int argc, char* argv[]) { |
| current->End(argc); |
| return true; |
| -} |
| +} |
| int Shell::RunMain(int argc, char* argv[]) { |
| +#ifndef D8_LIGHT |
| i::List<i::Thread*> threads(1); |
| - |
| +#endif // D8_LIGHT |
| { |
| +#ifndef D8_LIGHT |
| if (options.parallel_files != NULL) |
| for (int i = 0; i < options.parallel_files->length(); i++) { |
| i::Vector<const char> files = options.parallel_files->at(i); |
| @@ -1076,6 +1175,7 @@ int Shell::RunMain(int argc, char* argv[]) { |
| for (int i = 1; i < options.num_isolates; ++i) { |
| options.isolate_sources[i].StartExecuteInThread(); |
| } |
| +#endif // D8_LIGHT |
| Locker lock; |
| HandleScope scope; |
| @@ -1090,14 +1190,18 @@ int Shell::RunMain(int argc, char* argv[]) { |
| } else { |
| context.Dispose(); |
| } |
| + |
| +#ifndef D8_LIGHT |
| // Start preemption if threads have been created and preemption is enabled. |
| if (options.parallel_files != NULL |
| && threads.length() > 0 |
| && options.use_preemption) { |
| Locker::StartPreemption(options.preemption_interval); |
| } |
| +#endif // D8_LIGHT |
| } |
| +#ifndef D8_LIGHT |
| for (int i = 1; i < options.num_isolates; ++i) { |
| options.isolate_sources[i].WaitForThread(); |
| } |
| @@ -1110,6 +1214,7 @@ int Shell::RunMain(int argc, char* argv[]) { |
| } |
| OnExit(); |
| +#endif // D8_LIGHT |
| return 0; |
| } |
| @@ -1136,14 +1241,15 @@ int Shell::Main(int argc, char* argv[]) { |
| result = RunMain(argc, argv); |
| } |
| -#ifdef ENABLE_DEBUGGER_SUPPORT |
| + |
| +#if defined(D8_FULL) && defined(ENABLE_DEBUGGER_SUPPORT) |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
D8_FULL is newer defined. Did you mean !defined(V8
|
| // Run remote debugger if requested, but never on --test |
| if (i::FLAG_remote_debugger && !options.test_shell) { |
| InstallUtilityScript(); |
| RunRemoteDebugger(i::FLAG_debugger_port); |
| return 0; |
| } |
| -#endif |
| +#endif // D8_LIGHT && ENABLE_DEBUGGER_SUPPORT |
| // Run interactive shell if explicitly requested or if no script has been |
| // executed, but never on --test |
| @@ -1151,7 +1257,9 @@ int Shell::Main(int argc, char* argv[]) { |
| if (( options.interactive_shell |
| || !options.script_executed ) |
| && !options.test_shell ) { |
| +#ifndef D8_LIGHT |
| InstallUtilityScript(); |
| +#endif // D8_LIGHT |
| RunShell(); |
| } |
| @@ -1168,3 +1276,6 @@ int main(int argc, char* argv[]) { |
| return v8::Shell::Main(argc, argv); |
| } |
| #endif |
| + |
| +#undef D8_LIGHT |
| +#undef D8_FULL |
|
Søren Thygesen Gjesse
2011/07/13 14:12:38
D8_FULL?
|