OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #include "vm/regexp_assembler_bytecode.h" | |
6 | |
7 #include "vm/regexp_assembler_bytecode_inl.h" | |
8 #include "vm/exceptions.h" | |
9 #include "vm/object_store.h" | |
10 #include "vm/regexp_bytecodes.h" | |
11 #include "vm/regexp_assembler.h" | |
12 #include "vm/regexp.h" | |
13 #include "vm/regexp_parser.h" | |
14 #include "vm/regexp_interpreter.h" | |
15 | |
16 namespace dart { | |
17 | |
18 BytecodeRegExpMacroAssembler::BytecodeRegExpMacroAssembler( | |
19 ZoneGrowableArray<uint8_t>* buffer, | |
20 Zone* zone) | |
21 : RegExpMacroAssembler(zone), | |
22 buffer_(buffer), | |
23 pc_(0), | |
24 advance_current_end_(kInvalidPC) { } | |
25 | |
26 | |
27 BytecodeRegExpMacroAssembler::~BytecodeRegExpMacroAssembler() { | |
28 if (backtrack_.is_linked()) backtrack_.Unuse(); | |
29 } | |
30 | |
31 | |
32 BytecodeRegExpMacroAssembler::IrregexpImplementation | |
33 BytecodeRegExpMacroAssembler::Implementation() { | |
34 return kBytecodeImplementation; | |
35 } | |
36 | |
37 | |
38 void BytecodeRegExpMacroAssembler::BindBlock(BlockLabel* l) { | |
39 advance_current_end_ = kInvalidPC; | |
40 ASSERT(!l->is_bound()); | |
41 if (l->is_linked()) { | |
42 intptr_t pos = l->pos(); | |
43 while (pos != 0) { | |
44 intptr_t fixup = pos; | |
45 pos = *reinterpret_cast<int32_t*>(buffer_->data() + fixup); | |
46 *reinterpret_cast<uint32_t*>(buffer_->data() + fixup) = pc_; | |
47 } | |
48 } | |
49 l->bind_to(pc_); | |
50 } | |
51 | |
52 | |
53 void BytecodeRegExpMacroAssembler::EmitOrLink(BlockLabel* l) { | |
54 if (l == NULL) l = &backtrack_; | |
55 if (l->is_bound()) { | |
56 Emit32(l->pos()); | |
57 } else { | |
58 int pos = 0; | |
59 if (l->is_linked()) { | |
60 pos = l->pos(); | |
61 } | |
62 l->link_to(pc_); | |
63 Emit32(pos); | |
64 } | |
65 } | |
66 | |
67 | |
68 void BytecodeRegExpMacroAssembler::PopRegister(intptr_t register_index) { | |
69 ASSERT(register_index >= 0); | |
70 ASSERT(register_index <= kMaxRegister); | |
71 Emit(BC_POP_REGISTER, register_index); | |
72 } | |
73 | |
74 | |
75 void BytecodeRegExpMacroAssembler::PushRegister(intptr_t register_index) { | |
76 ASSERT(register_index >= 0); | |
77 ASSERT(register_index <= kMaxRegister); | |
78 Emit(BC_PUSH_REGISTER, register_index); | |
79 } | |
80 | |
81 | |
82 void BytecodeRegExpMacroAssembler::WriteCurrentPositionToRegister( | |
83 intptr_t register_index, intptr_t cp_offset) { | |
84 ASSERT(register_index >= 0); | |
85 ASSERT(register_index <= kMaxRegister); | |
86 Emit(BC_SET_REGISTER_TO_CP, register_index); | |
87 Emit32(cp_offset); // Current position offset. | |
88 } | |
89 | |
90 | |
91 void BytecodeRegExpMacroAssembler::ClearRegisters(intptr_t reg_from, | |
92 intptr_t reg_to) { | |
93 ASSERT(reg_from <= reg_to); | |
94 for (int reg = reg_from; reg <= reg_to; reg++) { | |
95 SetRegister(reg, -1); | |
96 } | |
97 } | |
98 | |
99 | |
100 void BytecodeRegExpMacroAssembler::ReadCurrentPositionFromRegister( | |
101 intptr_t register_index) { | |
102 ASSERT(register_index >= 0); | |
103 ASSERT(register_index <= kMaxRegister); | |
104 Emit(BC_SET_CP_TO_REGISTER, register_index); | |
105 } | |
106 | |
107 | |
108 void BytecodeRegExpMacroAssembler::WriteStackPointerToRegister( | |
109 intptr_t register_index) { | |
110 ASSERT(register_index >= 0); | |
111 ASSERT(register_index <= kMaxRegister); | |
112 Emit(BC_SET_REGISTER_TO_SP, register_index); | |
113 } | |
114 | |
115 | |
116 void BytecodeRegExpMacroAssembler::ReadStackPointerFromRegister( | |
117 intptr_t register_index) { | |
118 ASSERT(register_index >= 0); | |
119 ASSERT(register_index <= kMaxRegister); | |
120 Emit(BC_SET_SP_TO_REGISTER, register_index); | |
121 } | |
122 | |
123 | |
124 void BytecodeRegExpMacroAssembler::SetCurrentPositionFromEnd(intptr_t by) { | |
125 ASSERT(Utils::IsUint(24, by)); | |
126 Emit(BC_SET_CURRENT_POSITION_FROM_END, by); | |
127 } | |
128 | |
129 | |
130 void BytecodeRegExpMacroAssembler::SetRegister(intptr_t register_index, | |
131 intptr_t to) { | |
132 ASSERT(register_index >= 0); | |
133 ASSERT(register_index <= kMaxRegister); | |
134 Emit(BC_SET_REGISTER, register_index); | |
135 Emit32(to); | |
136 } | |
137 | |
138 | |
139 void BytecodeRegExpMacroAssembler::AdvanceRegister(intptr_t register_index, | |
140 intptr_t by) { | |
141 ASSERT(register_index >= 0); | |
142 ASSERT(register_index <= kMaxRegister); | |
143 Emit(BC_ADVANCE_REGISTER, register_index); | |
144 Emit32(by); | |
145 } | |
146 | |
147 | |
148 void BytecodeRegExpMacroAssembler::PopCurrentPosition() { | |
149 Emit(BC_POP_CP, 0); | |
150 } | |
151 | |
152 | |
153 void BytecodeRegExpMacroAssembler::PushCurrentPosition() { | |
154 Emit(BC_PUSH_CP, 0); | |
155 } | |
156 | |
157 | |
158 void BytecodeRegExpMacroAssembler::Backtrack() { | |
159 Emit(BC_POP_BT, 0); | |
160 } | |
161 | |
162 | |
163 void BytecodeRegExpMacroAssembler::GoTo(BlockLabel* l) { | |
164 if (advance_current_end_ == pc_) { | |
165 // Combine advance current and goto. | |
166 pc_ = advance_current_start_; | |
167 Emit(BC_ADVANCE_CP_AND_GOTO, advance_current_offset_); | |
168 EmitOrLink(l); | |
169 advance_current_end_ = kInvalidPC; | |
170 } else { | |
171 // Regular goto. | |
172 Emit(BC_GOTO, 0); | |
173 EmitOrLink(l); | |
174 } | |
175 } | |
176 | |
177 | |
178 void BytecodeRegExpMacroAssembler::PushBacktrack(BlockLabel* l) { | |
179 Emit(BC_PUSH_BT, 0); | |
180 EmitOrLink(l); | |
181 } | |
182 | |
183 | |
184 bool BytecodeRegExpMacroAssembler::Succeed() { | |
185 Emit(BC_SUCCEED, 0); | |
186 return false; // Restart matching for global regexp not supported. | |
187 } | |
188 | |
189 | |
190 void BytecodeRegExpMacroAssembler::Fail() { | |
191 Emit(BC_FAIL, 0); | |
192 } | |
193 | |
194 | |
195 void BytecodeRegExpMacroAssembler::AdvanceCurrentPosition(intptr_t by) { | |
196 ASSERT(by >= kMinCPOffset); | |
197 ASSERT(by <= kMaxCPOffset); | |
198 advance_current_start_ = pc_; | |
199 advance_current_offset_ = by; | |
200 Emit(BC_ADVANCE_CP, by); | |
201 advance_current_end_ = pc_; | |
202 } | |
203 | |
204 | |
205 void BytecodeRegExpMacroAssembler::CheckGreedyLoop( | |
206 BlockLabel* on_tos_equals_current_position) { | |
207 Emit(BC_CHECK_GREEDY, 0); | |
208 EmitOrLink(on_tos_equals_current_position); | |
209 } | |
210 | |
211 | |
212 void BytecodeRegExpMacroAssembler::LoadCurrentCharacter(intptr_t cp_offset, | |
213 BlockLabel* on_failure, | |
214 bool check_bounds, | |
215 intptr_t characters) { | |
216 ASSERT(cp_offset >= kMinCPOffset); | |
217 ASSERT(cp_offset <= kMaxCPOffset); | |
218 int bytecode; | |
219 if (check_bounds) { | |
220 if (characters == 4) { | |
221 bytecode = BC_LOAD_4_CURRENT_CHARS; | |
222 } else if (characters == 2) { | |
223 bytecode = BC_LOAD_2_CURRENT_CHARS; | |
224 } else { | |
225 ASSERT(characters == 1); | |
226 bytecode = BC_LOAD_CURRENT_CHAR; | |
227 } | |
228 } else { | |
229 if (characters == 4) { | |
230 bytecode = BC_LOAD_4_CURRENT_CHARS_UNCHECKED; | |
231 } else if (characters == 2) { | |
232 bytecode = BC_LOAD_2_CURRENT_CHARS_UNCHECKED; | |
233 } else { | |
234 ASSERT(characters == 1); | |
235 bytecode = BC_LOAD_CURRENT_CHAR_UNCHECKED; | |
236 } | |
237 } | |
238 Emit(bytecode, cp_offset); | |
239 if (check_bounds) EmitOrLink(on_failure); | |
240 } | |
241 | |
242 | |
243 void BytecodeRegExpMacroAssembler::CheckCharacterLT(uint16_t limit, | |
244 BlockLabel* on_less) { | |
245 Emit(BC_CHECK_LT, limit); | |
246 EmitOrLink(on_less); | |
247 } | |
248 | |
249 | |
250 void BytecodeRegExpMacroAssembler::CheckCharacterGT(uint16_t limit, | |
251 BlockLabel* on_greater) { | |
252 Emit(BC_CHECK_GT, limit); | |
253 EmitOrLink(on_greater); | |
254 } | |
255 | |
256 | |
257 void BytecodeRegExpMacroAssembler::CheckCharacter(uint32_t c, | |
258 BlockLabel* on_equal) { | |
259 if (c > MAX_FIRST_ARG) { | |
260 Emit(BC_CHECK_4_CHARS, 0); | |
261 Emit32(c); | |
262 } else { | |
263 Emit(BC_CHECK_CHAR, c); | |
264 } | |
265 EmitOrLink(on_equal); | |
266 } | |
267 | |
268 | |
269 void BytecodeRegExpMacroAssembler::CheckAtStart(BlockLabel* on_at_start) { | |
270 Emit(BC_CHECK_AT_START, 0); | |
271 EmitOrLink(on_at_start); | |
272 } | |
273 | |
274 | |
275 void BytecodeRegExpMacroAssembler::CheckNotAtStart( | |
276 BlockLabel* on_not_at_start) { | |
277 Emit(BC_CHECK_NOT_AT_START, 0); | |
278 EmitOrLink(on_not_at_start); | |
279 } | |
280 | |
281 | |
282 void BytecodeRegExpMacroAssembler::CheckNotCharacter(uint32_t c, | |
283 BlockLabel* on_not_equal) { | |
284 if (c > MAX_FIRST_ARG) { | |
285 Emit(BC_CHECK_NOT_4_CHARS, 0); | |
286 Emit32(c); | |
287 } else { | |
288 Emit(BC_CHECK_NOT_CHAR, c); | |
289 } | |
290 EmitOrLink(on_not_equal); | |
291 } | |
292 | |
293 | |
294 void BytecodeRegExpMacroAssembler::CheckCharacterAfterAnd( | |
295 uint32_t c, | |
296 uint32_t mask, | |
297 BlockLabel* on_equal) { | |
298 if (c > MAX_FIRST_ARG) { | |
299 Emit(BC_AND_CHECK_4_CHARS, 0); | |
300 Emit32(c); | |
301 } else { | |
302 Emit(BC_AND_CHECK_CHAR, c); | |
303 } | |
304 Emit32(mask); | |
305 EmitOrLink(on_equal); | |
306 } | |
307 | |
308 | |
309 void BytecodeRegExpMacroAssembler::CheckNotCharacterAfterAnd( | |
310 uint32_t c, | |
311 uint32_t mask, | |
312 BlockLabel* on_not_equal) { | |
313 if (c > MAX_FIRST_ARG) { | |
314 Emit(BC_AND_CHECK_NOT_4_CHARS, 0); | |
315 Emit32(c); | |
316 } else { | |
317 Emit(BC_AND_CHECK_NOT_CHAR, c); | |
318 } | |
319 Emit32(mask); | |
320 EmitOrLink(on_not_equal); | |
321 } | |
322 | |
323 | |
324 void BytecodeRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd( | |
325 uint16_t c, | |
326 uint16_t minus, | |
327 uint16_t mask, | |
328 BlockLabel* on_not_equal) { | |
329 Emit(BC_MINUS_AND_CHECK_NOT_CHAR, c); | |
330 Emit16(minus); | |
331 Emit16(mask); | |
332 EmitOrLink(on_not_equal); | |
333 } | |
334 | |
335 | |
336 void BytecodeRegExpMacroAssembler::CheckCharacterInRange( | |
337 uint16_t from, | |
338 uint16_t to, | |
339 BlockLabel* on_in_range) { | |
340 Emit(BC_CHECK_CHAR_IN_RANGE, 0); | |
341 Emit16(from); | |
342 Emit16(to); | |
343 EmitOrLink(on_in_range); | |
344 } | |
345 | |
346 | |
347 void BytecodeRegExpMacroAssembler::CheckCharacterNotInRange( | |
348 uint16_t from, | |
349 uint16_t to, | |
350 BlockLabel* on_not_in_range) { | |
351 Emit(BC_CHECK_CHAR_NOT_IN_RANGE, 0); | |
352 Emit16(from); | |
353 Emit16(to); | |
354 EmitOrLink(on_not_in_range); | |
355 } | |
356 | |
357 | |
358 void BytecodeRegExpMacroAssembler::CheckBitInTable( | |
359 const TypedData& table, BlockLabel* on_bit_set) { | |
360 Emit(BC_CHECK_BIT_IN_TABLE, 0); | |
361 EmitOrLink(on_bit_set); | |
362 for (int i = 0; i < kTableSize; i += kBitsPerByte) { | |
363 int byte = 0; | |
364 for (int j = 0; j < kBitsPerByte; j++) { | |
365 if (table.GetUint8(i + j) != 0) byte |= 1 << j; | |
366 } | |
367 Emit8(byte); | |
368 } | |
369 } | |
370 | |
371 | |
372 void BytecodeRegExpMacroAssembler::CheckNotBackReference( | |
373 intptr_t start_reg, | |
374 BlockLabel* on_not_equal) { | |
375 ASSERT(start_reg >= 0); | |
376 ASSERT(start_reg <= kMaxRegister); | |
377 Emit(BC_CHECK_NOT_BACK_REF, start_reg); | |
378 EmitOrLink(on_not_equal); | |
379 } | |
380 | |
381 | |
382 void BytecodeRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase( | |
383 intptr_t start_reg, | |
384 BlockLabel* on_not_equal) { | |
385 ASSERT(start_reg >= 0); | |
386 ASSERT(start_reg <= kMaxRegister); | |
387 Emit(BC_CHECK_NOT_BACK_REF_NO_CASE, start_reg); | |
388 EmitOrLink(on_not_equal); | |
389 } | |
390 | |
391 | |
392 void BytecodeRegExpMacroAssembler::IfRegisterLT(intptr_t register_index, | |
393 intptr_t comparand, | |
394 BlockLabel* on_less_than) { | |
395 ASSERT(register_index >= 0); | |
396 ASSERT(register_index <= kMaxRegister); | |
397 Emit(BC_CHECK_REGISTER_LT, register_index); | |
398 Emit32(comparand); | |
399 EmitOrLink(on_less_than); | |
400 } | |
401 | |
402 | |
403 void BytecodeRegExpMacroAssembler::IfRegisterGE( | |
404 intptr_t register_index, | |
405 intptr_t comparand, | |
406 BlockLabel* on_greater_or_equal) { | |
407 ASSERT(register_index >= 0); | |
408 ASSERT(register_index <= kMaxRegister); | |
409 Emit(BC_CHECK_REGISTER_GE, register_index); | |
410 Emit32(comparand); | |
411 EmitOrLink(on_greater_or_equal); | |
412 } | |
413 | |
414 | |
415 void BytecodeRegExpMacroAssembler::IfRegisterEqPos(intptr_t register_index, | |
416 BlockLabel* on_eq) { | |
417 ASSERT(register_index >= 0); | |
418 ASSERT(register_index <= kMaxRegister); | |
419 Emit(BC_CHECK_REGISTER_EQ_POS, register_index); | |
420 EmitOrLink(on_eq); | |
421 } | |
422 | |
423 | |
424 RawTypedData* BytecodeRegExpMacroAssembler::GetBytecode() { | |
425 BindBlock(&backtrack_); | |
426 Emit(BC_POP_BT, 0); | |
427 | |
428 intptr_t len = length(); | |
429 const TypedData& bytecode = | |
430 TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, len)); | |
431 | |
432 NoSafepointScope no_safepoint; | |
433 memmove(bytecode.DataAddr(0), buffer_->data(), len); | |
434 | |
435 return bytecode.raw(); | |
436 } | |
437 | |
438 | |
439 intptr_t BytecodeRegExpMacroAssembler::length() { | |
440 return pc_; | |
441 } | |
442 | |
443 | |
444 void BytecodeRegExpMacroAssembler::Expand() { | |
445 // BOGUS | |
446 buffer_->Add(0); | |
447 buffer_->Add(0); | |
448 buffer_->Add(0); | |
449 buffer_->Add(0); | |
450 intptr_t x = buffer_->length(); | |
451 for (intptr_t i = 0; i < x; i++) buffer_->Add(0); | |
452 } | |
453 | |
454 | |
455 static intptr_t Prepare(const JSRegExp& regexp, | |
456 const String& subject, | |
457 Zone* zone) { | |
458 bool is_one_byte = subject.IsOneByteString() || | |
459 subject.IsExternalOneByteString(); | |
460 | |
461 if (regexp.bytecode(is_one_byte) == TypedData::null()) { | |
462 const String& pattern = String::Handle(zone, regexp.pattern()); | |
463 | |
464 const bool multiline = regexp.is_multi_line(); | |
465 RegExpCompileData* compile_data = new(zone) RegExpCompileData(); | |
466 if (!RegExpParser::ParseRegExp(pattern, multiline, compile_data)) { | |
467 // Parsing failures are handled in the JSRegExp factory constructor. | |
468 UNREACHABLE(); | |
469 } | |
470 | |
471 regexp.set_num_bracket_expressions(compile_data->capture_count); | |
472 if (compile_data->simple) { | |
473 regexp.set_is_simple(); | |
474 } else { | |
475 regexp.set_is_complex(); | |
476 } | |
477 | |
478 RegExpEngine::CompilationResult result = | |
479 RegExpEngine::CompileBytecode(compile_data, regexp, is_one_byte, zone); | |
480 ASSERT(result.bytecode != NULL); | |
481 ASSERT((regexp.num_registers() == -1) || | |
482 (regexp.num_registers() == result.num_registers)); | |
483 regexp.set_num_registers(result.num_registers); | |
484 regexp.set_bytecode(is_one_byte, *(result.bytecode)); | |
485 } | |
486 | |
487 ASSERT(regexp.num_registers() != -1); | |
488 | |
489 return regexp.num_registers() + | |
490 (Smi::Value(regexp.num_bracket_expressions()) + 1) * 2; | |
491 } | |
492 | |
493 | |
494 static IrregexpInterpreter::IrregexpResult ExecRaw(const JSRegExp& regexp, | |
495 const String& subject, | |
496 intptr_t index, | |
497 int32_t* output, | |
498 intptr_t output_size, | |
499 Zone* zone) { | |
500 bool is_one_byte = subject.IsOneByteString() || | |
501 subject.IsExternalOneByteString(); | |
502 | |
503 ASSERT(regexp.num_bracket_expressions() != Smi::null()); | |
504 | |
505 // We must have done EnsureCompiledIrregexp, so we can get the number of | |
506 // registers. | |
507 int number_of_capture_registers = | |
508 (Smi::Value(regexp.num_bracket_expressions()) + 1) * 2; | |
509 int32_t* raw_output = &output[number_of_capture_registers]; | |
510 | |
511 // We do not touch the actual capture result registers until we know there | |
512 // has been a match so that we can use those capture results to set the | |
513 // last match info. | |
514 for (int i = number_of_capture_registers - 1; i >= 0; i--) { | |
515 raw_output[i] = -1; | |
516 } | |
517 | |
518 const TypedData& bytecode = | |
519 TypedData::Handle(zone, regexp.bytecode(is_one_byte)); | |
520 ASSERT(!bytecode.IsNull()); | |
521 IrregexpInterpreter::IrregexpResult result = | |
522 IrregexpInterpreter::Match(bytecode, subject, raw_output, index, zone); | |
523 | |
524 if (result == IrregexpInterpreter::RE_SUCCESS) { | |
525 // Copy capture results to the start of the registers array. | |
526 memmove(output, raw_output, number_of_capture_registers * sizeof(int32_t)); | |
527 } | |
528 if (result == IrregexpInterpreter::RE_EXCEPTION) { | |
529 Thread* thread = Thread::Current(); | |
530 Isolate* isolate = thread->isolate(); | |
531 const Instance& exception = | |
532 Instance::Handle(isolate->object_store()->stack_overflow()); | |
533 Exceptions::Throw(thread, exception); | |
534 UNREACHABLE(); | |
535 } | |
536 return result; | |
537 } | |
538 | |
539 | |
540 RawInstance* BytecodeRegExpMacroAssembler::Interpret(const JSRegExp& regexp, | |
541 const String& subject, | |
542 const Smi& start_index, | |
543 Zone* zone) { | |
544 intptr_t required_registers = Prepare(regexp, subject, zone); | |
545 if (required_registers < 0) { | |
546 // Compiling failed with an exception. | |
547 UNREACHABLE(); | |
548 } | |
549 | |
550 // V8 uses a shared copy on the isolate when smaller than some threshold. | |
551 int32_t* output_registers = zone->Alloc<int32_t>(required_registers); | |
552 | |
553 IrregexpInterpreter::IrregexpResult result = ExecRaw(regexp, | |
554 subject, | |
555 start_index.Value(), | |
556 output_registers, | |
557 required_registers, | |
558 zone); | |
559 | |
560 if (result == IrregexpInterpreter::RE_SUCCESS) { | |
561 intptr_t capture_count = Smi::Value(regexp.num_bracket_expressions()); | |
562 intptr_t capture_register_count = (capture_count + 1) * 2; | |
563 ASSERT(required_registers >= capture_register_count); | |
564 | |
565 const TypedData& result = | |
rmacnak
2015/06/25 00:14:47
In V8, the result array is provided by the caller
| |
566 TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, | |
567 capture_register_count)); | |
568 { | |
569 #ifdef DEBUG | |
570 // These indices will be used with substring operations that don't check | |
571 // bounds, so sanity check them here. | |
572 for (intptr_t i = 0; i < capture_register_count; i++) { | |
573 int32_t val = output_registers[i]; | |
574 ASSERT(val == -1 || (val >= 0 && val <= subject.Length())); | |
575 } | |
576 #endif | |
577 | |
578 NoSafepointScope no_safepoint; | |
579 memmove(result.DataAddr(0), | |
580 output_registers, | |
581 capture_register_count * sizeof(int32_t)); | |
582 } | |
583 | |
584 return result.raw(); | |
585 } | |
586 if (result == IrregexpInterpreter::RE_EXCEPTION) { | |
587 UNREACHABLE(); | |
588 } | |
589 ASSERT(result == IrregexpInterpreter::RE_FAILURE); | |
590 return Instance::null(); | |
591 } | |
592 | |
593 | |
594 } // namespace dart | |
OLD | NEW |