blob: 5c3da13a67c4592c026a6a86835c5eddeb321340 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2008 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28String.prototype.startsWith = function (str) {
29 if (str.length > this.length)
30 return false;
31 return this.substr(0, str.length) == str;
32}
33
34function log10(num) {
35 return Math.log(num)/Math.log(10);
36}
37
38function ToInspectableObject(obj) {
39 if (!obj && typeof obj === 'object') {
40 return void 0;
41 } else {
42 return Object(obj);
43 }
44}
45
46function GetCompletions(global, last, full) {
47 var full_tokens = full.split();
48 full = full_tokens.pop();
49 var parts = full.split('.');
50 parts.pop();
51 var current = global;
52 for (var i = 0; i < parts.length; i++) {
53 var part = parts[i];
54 var next = current[part];
55 if (!next)
56 return [];
57 current = next;
58 }
59 var result = [];
60 current = ToInspectableObject(current);
61 while (typeof current !== 'undefined') {
62 var mirror = new $debug.ObjectMirror(current);
63 var properties = mirror.properties();
64 for (var i = 0; i < properties.length; i++) {
65 var name = properties[i].name();
66 if (typeof name === 'string' && name.startsWith(last))
67 result.push(name);
68 }
69 current = ToInspectableObject(current.__proto__);
70 }
71 return result;
72}
73
74
75// Global object holding debugger related constants and state.
76const Debug = {};
77
78
79// Debug events which can occour in the V8 JavaScript engine. These originate
80// from the API include file v8-debug.h.
81Debug.DebugEvent = { Break: 1,
82 Exception: 2,
83 NewFunction: 3,
84 BeforeCompile: 4,
85 AfterCompile: 5 };
86
87
88// The different types of scripts matching enum ScriptType in objects.h.
89Debug.ScriptType = { Native: 0,
90 Extension: 1,
91 Normal: 2 };
92
93
94// The different types of script compilations matching enum
95// Script::CompilationType in objects.h.
96Debug.ScriptCompilationType = { Host: 0,
97 Eval: 1,
98 JSON: 2 };
99
100
101// The different types of scopes matching constants runtime.cc.
102Debug.ScopeType = { Global: 0,
103 Local: 1,
104 With: 2,
105 Closure: 3,
106 Catch: 4 };
107
108
109// Current debug state.
110const kNoFrame = -1;
111Debug.State = {
112 currentFrame: kNoFrame,
113 currentSourceLine: -1
114}
115var trace_compile = false; // Tracing all compile events?
116
117
118// Process a debugger JSON message into a display text and a running status.
119// This function returns an object with properties "text" and "running" holding
120// this information.
121function DebugMessageDetails(message) {
122 // Convert the JSON string to an object.
123 var response = new ProtocolPackage(message);
124
125 if (response.type() == 'event') {
126 return DebugEventDetails(response);
127 } else {
128 return DebugResponseDetails(response);
129 }
130}
131
132function DebugEventDetails(response) {
133 details = {text:'', running:false}
134
135 // Get the running state.
136 details.running = response.running();
137
138 var body = response.body();
139 var result = '';
140 switch (response.event()) {
141 case 'break':
142 if (body.breakpoints) {
143 result += 'breakpoint';
144 if (body.breakpoints.length > 1) {
145 result += 's';
146 }
147 result += ' #';
148 for (var i = 0; i < body.breakpoints.length; i++) {
149 if (i > 0) {
150 result += ', #';
151 }
152 result += body.breakpoints[i];
153 }
154 } else {
155 result += 'break';
156 }
157 result += ' in ';
158 result += body.invocationText;
159 result += ', ';
160 result += SourceInfo(body);
161 result += '\n';
162 result += SourceUnderline(body.sourceLineText, body.sourceColumn);
163 Debug.State.currentSourceLine = body.sourceLine;
164 Debug.State.currentFrame = 0;
165 details.text = result;
166 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100167
Steve Blocka7e24c12009-10-30 11:49:00 +0000168 case 'exception':
169 if (body.uncaught) {
170 result += 'Uncaught: ';
171 } else {
172 result += 'Exception: ';
173 }
174 result += '"';
175 result += body.exception.text;
176 result += '"';
177 if (body.sourceLine >= 0) {
178 result += ', ';
179 result += SourceInfo(body);
180 result += '\n';
181 result += SourceUnderline(body.sourceLineText, body.sourceColumn);
182 Debug.State.currentSourceLine = body.sourceLine;
183 Debug.State.currentFrame = 0;
184 } else {
185 result += ' (empty stack)';
186 Debug.State.currentSourceLine = -1;
187 Debug.State.currentFrame = kNoFrame;
188 }
189 details.text = result;
190 break;
191
192 case 'afterCompile':
193 if (trace_compile) {
194 result = 'Source ' + body.script.name + ' compiled:\n'
195 var source = body.script.source;
196 if (!(source[source.length - 1] == '\n')) {
197 result += source;
198 } else {
199 result += source.substring(0, source.length - 1);
200 }
201 }
202 details.text = result;
203 break;
204
205 default:
206 details.text = 'Unknown debug event ' + response.event();
207 }
208
209 return details;
210};
211
212
213function SourceInfo(body) {
214 var result = '';
Steve Block6ded16b2010-05-10 14:33:55 +0100215
Steve Blocka7e24c12009-10-30 11:49:00 +0000216 if (body.script) {
217 if (body.script.name) {
218 result += body.script.name;
219 } else {
220 result += '[unnamed]';
221 }
222 }
223 result += ' line ';
224 result += body.sourceLine + 1;
225 result += ' column ';
226 result += body.sourceColumn + 1;
Steve Block6ded16b2010-05-10 14:33:55 +0100227
Steve Blocka7e24c12009-10-30 11:49:00 +0000228 return result;
229}
230
231
232function SourceUnderline(source_text, position) {
233 if (!source_text) {
234 return;
235 }
236
237 // Create an underline with a caret pointing to the source position. If the
238 // source contains a tab character the underline will have a tab character in
239 // the same place otherwise the underline will have a space character.
240 var underline = '';
241 for (var i = 0; i < position; i++) {
242 if (source_text[i] == '\t') {
243 underline += '\t';
244 } else {
245 underline += ' ';
246 }
247 }
248 underline += '^';
249
250 // Return the source line text with the underline beneath.
251 return source_text + '\n' + underline;
252};
253
254
255// Converts a text command to a JSON request.
256function DebugCommandToJSONRequest(cmd_line) {
257 return new DebugRequest(cmd_line).JSONRequest();
258};
259
260
261function DebugRequest(cmd_line) {
262 // If the very first character is a { assume that a JSON request have been
263 // entered as a command. Converting that to a JSON request is trivial.
264 if (cmd_line && cmd_line.length > 0 && cmd_line.charAt(0) == '{') {
265 this.request_ = cmd_line;
266 return;
267 }
268
269 // Trim string for leading and trailing whitespace.
270 cmd_line = cmd_line.replace(/^\s+|\s+$/g, '');
271
272 // Find the command.
273 var pos = cmd_line.indexOf(' ');
274 var cmd;
275 var args;
276 if (pos == -1) {
277 cmd = cmd_line;
278 args = '';
279 } else {
280 cmd = cmd_line.slice(0, pos);
281 args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, '');
282 }
283
284 // Switch on command.
285 switch (cmd) {
286 case 'continue':
287 case 'c':
288 this.request_ = this.continueCommandToJSONRequest_(args);
289 break;
290
291 case 'step':
292 case 's':
293 this.request_ = this.stepCommandToJSONRequest_(args);
294 break;
295
296 case 'backtrace':
297 case 'bt':
298 this.request_ = this.backtraceCommandToJSONRequest_(args);
299 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100300
Steve Blocka7e24c12009-10-30 11:49:00 +0000301 case 'frame':
302 case 'f':
303 this.request_ = this.frameCommandToJSONRequest_(args);
304 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100305
Steve Blocka7e24c12009-10-30 11:49:00 +0000306 case 'scopes':
307 this.request_ = this.scopesCommandToJSONRequest_(args);
308 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100309
Steve Blocka7e24c12009-10-30 11:49:00 +0000310 case 'scope':
311 this.request_ = this.scopeCommandToJSONRequest_(args);
312 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100313
Steve Blocka7e24c12009-10-30 11:49:00 +0000314 case 'print':
315 case 'p':
316 this.request_ = this.printCommandToJSONRequest_(args);
317 break;
318
319 case 'dir':
320 this.request_ = this.dirCommandToJSONRequest_(args);
321 break;
322
323 case 'references':
324 this.request_ = this.referencesCommandToJSONRequest_(args);
325 break;
326
327 case 'instances':
328 this.request_ = this.instancesCommandToJSONRequest_(args);
329 break;
330
331 case 'source':
332 this.request_ = this.sourceCommandToJSONRequest_(args);
333 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100334
Steve Blocka7e24c12009-10-30 11:49:00 +0000335 case 'scripts':
336 this.request_ = this.scriptsCommandToJSONRequest_(args);
337 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100338
Steve Blocka7e24c12009-10-30 11:49:00 +0000339 case 'break':
340 case 'b':
341 this.request_ = this.breakCommandToJSONRequest_(args);
342 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100343
Kristian Monsen25f61362010-05-21 11:50:48 +0100344 case 'breakpoints':
345 case 'bb':
346 this.request_ = this.breakpointsCommandToJSONRequest_(args);
347 break;
348
Steve Blocka7e24c12009-10-30 11:49:00 +0000349 case 'clear':
350 this.request_ = this.clearCommandToJSONRequest_(args);
351 break;
352
353 case 'threads':
354 this.request_ = this.threadsCommandToJSONRequest_(args);
355 break;
356
357 case 'trace':
358 // Return undefined to indicate command handled internally (no JSON).
359 this.request_ = void 0;
360 this.traceCommand_(args);
361 break;
362
363 case 'help':
364 case '?':
365 this.helpCommand_(args);
366 // Return undefined to indicate command handled internally (no JSON).
367 this.request_ = void 0;
368 break;
369
370 default:
371 throw new Error('Unknown command "' + cmd + '"');
372 }
Steve Block6ded16b2010-05-10 14:33:55 +0100373
Steve Blocka7e24c12009-10-30 11:49:00 +0000374 last_cmd = cmd;
375}
376
377DebugRequest.prototype.JSONRequest = function() {
378 return this.request_;
379}
380
381
382function RequestPacket(command) {
383 this.seq = 0;
384 this.type = 'request';
385 this.command = command;
386}
387
388
389RequestPacket.prototype.toJSONProtocol = function() {
390 // Encode the protocol header.
391 var json = '{';
392 json += '"seq":' + this.seq;
393 json += ',"type":"' + this.type + '"';
394 if (this.command) {
395 json += ',"command":' + StringToJSON_(this.command);
396 }
397 if (this.arguments) {
398 json += ',"arguments":';
399 // Encode the arguments part.
400 if (this.arguments.toJSONProtocol) {
401 json += this.arguments.toJSONProtocol()
402 } else {
403 json += SimpleObjectToJSON_(this.arguments);
404 }
405 }
406 json += '}';
407 return json;
408}
409
410
411DebugRequest.prototype.createRequest = function(command) {
412 return new RequestPacket(command);
413};
414
415
416// Create a JSON request for the evaluation command.
417DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) {
418 // Global varaible used to store whether a handle was requested.
419 lookup_handle = null;
420 // Check if the expression is a handle id in the form #<handle>#.
421 var handle_match = expression.match(/^#([0-9]*)#$/);
422 if (handle_match) {
423 // Remember the handle requested in a global variable.
424 lookup_handle = parseInt(handle_match[1]);
425 // Build a lookup request.
426 var request = this.createRequest('lookup');
427 request.arguments = {};
428 request.arguments.handles = [ lookup_handle ];
429 return request.toJSONProtocol();
430 } else {
431 // Build an evaluate request.
432 var request = this.createRequest('evaluate');
433 request.arguments = {};
434 request.arguments.expression = expression;
435 // Request a global evaluation if there is no current frame.
436 if (Debug.State.currentFrame == kNoFrame) {
437 request.arguments.global = true;
438 }
439 return request.toJSONProtocol();
440 }
441};
442
443
444// Create a JSON request for the references/instances command.
445DebugRequest.prototype.makeReferencesJSONRequest_ = function(handle, type) {
446 // Build a references request.
447 var handle_match = handle.match(/^#([0-9]*)#$/);
448 if (handle_match) {
449 var request = this.createRequest('references');
450 request.arguments = {};
451 request.arguments.type = type;
452 request.arguments.handle = parseInt(handle_match[1]);
453 return request.toJSONProtocol();
454 } else {
455 throw new Error('Invalid object id.');
456 }
457};
458
459
460// Create a JSON request for the continue command.
461DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) {
462 var request = this.createRequest('continue');
463 return request.toJSONProtocol();
464};
465
466
467// Create a JSON request for the step command.
468DebugRequest.prototype.stepCommandToJSONRequest_ = function(args) {
469 // Requesting a step is through the continue command with additional
470 // arguments.
471 var request = this.createRequest('continue');
472 request.arguments = {};
473
474 // Process arguments if any.
475 if (args && args.length > 0) {
476 args = args.split(/\s*[ ]+\s*/g);
477
478 if (args.length > 2) {
479 throw new Error('Invalid step arguments.');
480 }
481
482 if (args.length > 0) {
483 // Get step count argument if any.
484 if (args.length == 2) {
485 var stepcount = parseInt(args[1]);
486 if (isNaN(stepcount) || stepcount <= 0) {
487 throw new Error('Invalid step count argument "' + args[0] + '".');
488 }
489 request.arguments.stepcount = stepcount;
490 }
491
492 // Get the step action.
493 switch (args[0]) {
494 case 'in':
495 case 'i':
496 request.arguments.stepaction = 'in';
497 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100498
Steve Blocka7e24c12009-10-30 11:49:00 +0000499 case 'min':
500 case 'm':
501 request.arguments.stepaction = 'min';
502 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100503
Steve Blocka7e24c12009-10-30 11:49:00 +0000504 case 'next':
505 case 'n':
506 request.arguments.stepaction = 'next';
507 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100508
Steve Blocka7e24c12009-10-30 11:49:00 +0000509 case 'out':
510 case 'o':
511 request.arguments.stepaction = 'out';
512 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100513
Steve Blocka7e24c12009-10-30 11:49:00 +0000514 default:
515 throw new Error('Invalid step argument "' + args[0] + '".');
516 }
517 }
518 } else {
519 // Default is step next.
520 request.arguments.stepaction = 'next';
521 }
522
523 return request.toJSONProtocol();
524};
525
526
527// Create a JSON request for the backtrace command.
528DebugRequest.prototype.backtraceCommandToJSONRequest_ = function(args) {
529 // Build a backtrace request from the text command.
530 var request = this.createRequest('backtrace');
Steve Block6ded16b2010-05-10 14:33:55 +0100531
Steve Blocka7e24c12009-10-30 11:49:00 +0000532 // Default is to show top 10 frames.
533 request.arguments = {};
534 request.arguments.fromFrame = 0;
535 request.arguments.toFrame = 10;
536
537 args = args.split(/\s*[ ]+\s*/g);
538 if (args.length == 1 && args[0].length > 0) {
539 var frameCount = parseInt(args[0]);
540 if (frameCount > 0) {
541 // Show top frames.
542 request.arguments.fromFrame = 0;
543 request.arguments.toFrame = frameCount;
544 } else {
545 // Show bottom frames.
546 request.arguments.fromFrame = 0;
547 request.arguments.toFrame = -frameCount;
548 request.arguments.bottom = true;
549 }
550 } else if (args.length == 2) {
551 var fromFrame = parseInt(args[0]);
552 var toFrame = parseInt(args[1]);
553 if (isNaN(fromFrame) || fromFrame < 0) {
554 throw new Error('Invalid start frame argument "' + args[0] + '".');
555 }
556 if (isNaN(toFrame) || toFrame < 0) {
557 throw new Error('Invalid end frame argument "' + args[1] + '".');
558 }
559 if (fromFrame > toFrame) {
560 throw new Error('Invalid arguments start frame cannot be larger ' +
561 'than end frame.');
562 }
563 // Show frame range.
564 request.arguments.fromFrame = fromFrame;
565 request.arguments.toFrame = toFrame + 1;
566 } else if (args.length > 2) {
567 throw new Error('Invalid backtrace arguments.');
568 }
569
570 return request.toJSONProtocol();
571};
572
573
574// Create a JSON request for the frame command.
575DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) {
576 // Build a frame request from the text command.
577 var request = this.createRequest('frame');
578 args = args.split(/\s*[ ]+\s*/g);
579 if (args.length > 0 && args[0].length > 0) {
580 request.arguments = {};
581 request.arguments.number = args[0];
582 }
583 return request.toJSONProtocol();
584};
585
586
587// Create a JSON request for the scopes command.
588DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) {
589 // Build a scopes request from the text command.
590 var request = this.createRequest('scopes');
591 return request.toJSONProtocol();
592};
593
594
595// Create a JSON request for the scope command.
596DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) {
597 // Build a scope request from the text command.
598 var request = this.createRequest('scope');
599 args = args.split(/\s*[ ]+\s*/g);
600 if (args.length > 0 && args[0].length > 0) {
601 request.arguments = {};
602 request.arguments.number = args[0];
603 }
604 return request.toJSONProtocol();
605};
606
607
608// Create a JSON request for the print command.
609DebugRequest.prototype.printCommandToJSONRequest_ = function(args) {
610 // Build an evaluate request from the text command.
611 if (args.length == 0) {
612 throw new Error('Missing expression.');
613 }
614 return this.makeEvaluateJSONRequest_(args);
615};
616
617
618// Create a JSON request for the dir command.
619DebugRequest.prototype.dirCommandToJSONRequest_ = function(args) {
620 // Build an evaluate request from the text command.
621 if (args.length == 0) {
622 throw new Error('Missing expression.');
623 }
624 return this.makeEvaluateJSONRequest_(args);
625};
626
627
628// Create a JSON request for the references command.
629DebugRequest.prototype.referencesCommandToJSONRequest_ = function(args) {
630 // Build an evaluate request from the text command.
631 if (args.length == 0) {
632 throw new Error('Missing object id.');
633 }
Steve Block6ded16b2010-05-10 14:33:55 +0100634
Steve Blocka7e24c12009-10-30 11:49:00 +0000635 return this.makeReferencesJSONRequest_(args, 'referencedBy');
636};
637
638
639// Create a JSON request for the instances command.
640DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) {
641 // Build an evaluate request from the text command.
642 if (args.length == 0) {
643 throw new Error('Missing object id.');
644 }
Steve Block6ded16b2010-05-10 14:33:55 +0100645
Steve Blocka7e24c12009-10-30 11:49:00 +0000646 // Build a references request.
647 return this.makeReferencesJSONRequest_(args, 'constructedBy');
648};
649
650
651// Create a JSON request for the source command.
652DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) {
653 // Build a evaluate request from the text command.
654 var request = this.createRequest('source');
655
656 // Default is ten lines starting five lines before the current location.
657 var from = Debug.State.currentSourceLine - 5;
658 var lines = 10;
659
660 // Parse the arguments.
661 args = args.split(/\s*[ ]+\s*/g);
662 if (args.length > 1 && args[0].length > 0 && args[1].length > 0) {
663 from = parseInt(args[0]) - 1;
664 lines = parseInt(args[1]);
665 } else if (args.length > 0 && args[0].length > 0) {
666 from = parseInt(args[0]) - 1;
667 }
668
669 if (from < 0) from = 0;
670 if (lines < 0) lines = 10;
671
672 // Request source arround current source location.
673 request.arguments = {};
674 request.arguments.fromLine = from;
675 request.arguments.toLine = from + lines;
676
677 return request.toJSONProtocol();
678};
679
680
681// Create a JSON request for the scripts command.
682DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) {
683 // Build a evaluate request from the text command.
684 var request = this.createRequest('scripts');
685
686 // Process arguments if any.
687 if (args && args.length > 0) {
688 args = args.split(/\s*[ ]+\s*/g);
689
690 if (args.length > 1) {
691 throw new Error('Invalid scripts arguments.');
692 }
693
694 request.arguments = {};
695 switch (args[0]) {
696 case 'natives':
697 request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Native);
698 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100699
Steve Blocka7e24c12009-10-30 11:49:00 +0000700 case 'extensions':
701 request.arguments.types = ScriptTypeFlag(Debug.ScriptType.Extension);
702 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100703
Steve Blocka7e24c12009-10-30 11:49:00 +0000704 case 'all':
705 request.arguments.types =
706 ScriptTypeFlag(Debug.ScriptType.Normal) |
707 ScriptTypeFlag(Debug.ScriptType.Native) |
708 ScriptTypeFlag(Debug.ScriptType.Extension);
709 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100710
Steve Blocka7e24c12009-10-30 11:49:00 +0000711 default:
712 throw new Error('Invalid argument "' + args[0] + '".');
713 }
714 }
715
716 return request.toJSONProtocol();
717};
718
719
720// Create a JSON request for the break command.
721DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) {
722 // Build a evaluate request from the text command.
Steve Blocka7e24c12009-10-30 11:49:00 +0000723 // Process arguments if any.
724 if (args && args.length > 0) {
725 var target = args;
726 var type = 'function';
727 var line;
728 var column;
729 var condition;
730 var pos;
731
Steve Block6ded16b2010-05-10 14:33:55 +0100732 var request = this.createRequest('setbreakpoint');
733
Steve Blocka7e24c12009-10-30 11:49:00 +0000734 // Check for breakpoint condition.
735 pos = args.indexOf(' ');
736 if (pos > 0) {
737 target = args.substring(0, pos);
738 condition = args.substring(pos + 1, args.length);
739 }
740
741 // Check for script breakpoint (name:line[:column]). If no ':' in break
742 // specification it is considered a function break point.
743 pos = target.indexOf(':');
744 if (pos > 0) {
745 type = 'script';
746 var tmp = target.substring(pos + 1, target.length);
747 target = target.substring(0, pos);
Steve Block6ded16b2010-05-10 14:33:55 +0100748
Steve Blocka7e24c12009-10-30 11:49:00 +0000749 // Check for both line and column.
750 pos = tmp.indexOf(':');
751 if (pos > 0) {
752 column = parseInt(tmp.substring(pos + 1, tmp.length)) - 1;
753 line = parseInt(tmp.substring(0, pos)) - 1;
754 } else {
755 line = parseInt(tmp) - 1;
756 }
757 } else if (target[0] == '#' && target[target.length - 1] == '#') {
758 type = 'handle';
759 target = target.substring(1, target.length - 1);
760 } else {
761 type = 'function';
762 }
Steve Block6ded16b2010-05-10 14:33:55 +0100763
Steve Blocka7e24c12009-10-30 11:49:00 +0000764 request.arguments = {};
765 request.arguments.type = type;
766 request.arguments.target = target;
767 request.arguments.line = line;
768 request.arguments.column = column;
769 request.arguments.condition = condition;
770 } else {
Steve Block6ded16b2010-05-10 14:33:55 +0100771 var request = this.createRequest('suspend');
Steve Blocka7e24c12009-10-30 11:49:00 +0000772 }
773
774 return request.toJSONProtocol();
775};
776
777
Kristian Monsen25f61362010-05-21 11:50:48 +0100778DebugRequest.prototype.breakpointsCommandToJSONRequest_ = function(args) {
779 if (args && args.length > 0) {
780 throw new Error('Unexpected arguments.');
781 }
782 var request = this.createRequest('listbreakpoints');
783 return request.toJSONProtocol();
784};
785
786
Steve Blocka7e24c12009-10-30 11:49:00 +0000787// Create a JSON request for the clear command.
788DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
789 // Build a evaluate request from the text command.
790 var request = this.createRequest('clearbreakpoint');
791
792 // Process arguments if any.
793 if (args && args.length > 0) {
794 request.arguments = {};
795 request.arguments.breakpoint = parseInt(args);
796 } else {
797 throw new Error('Invalid break arguments.');
798 }
799
800 return request.toJSONProtocol();
801};
802
803
804// Create a JSON request for the threads command.
805DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
806 // Build a threads request from the text command.
807 var request = this.createRequest('threads');
808 return request.toJSONProtocol();
809};
810
811
812// Handle the trace command.
813DebugRequest.prototype.traceCommand_ = function(args) {
814 // Process arguments.
815 if (args && args.length > 0) {
816 if (args == 'compile') {
817 trace_compile = !trace_compile;
818 print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off'));
819 } else {
820 throw new Error('Invalid trace arguments.');
821 }
822 } else {
823 throw new Error('Invalid trace arguments.');
824 }
825}
826
827// Handle the help command.
828DebugRequest.prototype.helpCommand_ = function(args) {
829 // Help os quite simple.
830 if (args && args.length > 0) {
831 print('warning: arguments to \'help\' are ignored');
832 }
833
Steve Block6ded16b2010-05-10 14:33:55 +0100834 print('break');
Steve Blocka7e24c12009-10-30 11:49:00 +0000835 print('break location [condition]');
836 print(' break on named function: location is a function name');
837 print(' break on function: location is #<id>#');
838 print(' break on script position: location is name:line[:column]');
839 print('clear <breakpoint #>');
840 print('backtrace [n] | [-n] | [from to]');
841 print('frame <frame #>');
842 print('scopes');
843 print('scope <scope #>');
844 print('step [in | next | out| min [step count]]');
845 print('print <expression>');
846 print('dir <expression>');
847 print('source [from line [num lines]]');
848 print('scripts');
849 print('continue');
850 print('trace compile');
851 print('help');
852}
853
854
855function formatHandleReference_(value) {
856 if (value.handle() >= 0) {
857 return '#' + value.handle() + '#';
858 } else {
859 return '#Transient#';
860 }
861}
862
863
864function formatObject_(value, include_properties) {
865 var result = '';
866 result += formatHandleReference_(value);
867 result += ', type: object'
868 result += ', constructor ';
869 var ctor = value.constructorFunctionValue();
870 result += formatHandleReference_(ctor);
871 result += ', __proto__ ';
872 var proto = value.protoObjectValue();
873 result += formatHandleReference_(proto);
874 result += ', ';
875 result += value.propertyCount();
876 result += ' properties.';
877 if (include_properties) {
878 result += '\n';
879 for (var i = 0; i < value.propertyCount(); i++) {
880 result += ' ';
881 result += value.propertyName(i);
882 result += ': ';
883 var property_value = value.propertyValue(i);
884 if (property_value instanceof ProtocolReference) {
885 result += '<no type>';
886 } else {
887 if (property_value && property_value.type()) {
888 result += property_value.type();
889 } else {
890 result += '<no type>';
891 }
892 }
893 result += ' ';
894 result += formatHandleReference_(property_value);
895 result += '\n';
896 }
897 }
898 return result;
899}
900
901
902function formatScope_(scope) {
903 var result = '';
904 var index = scope.index;
905 result += '#' + (index <= 9 ? '0' : '') + index;
906 result += ' ';
907 switch (scope.type) {
908 case Debug.ScopeType.Global:
909 result += 'Global, ';
910 result += '#' + scope.object.ref + '#';
911 break;
912 case Debug.ScopeType.Local:
913 result += 'Local';
914 break;
915 case Debug.ScopeType.With:
916 result += 'With, ';
917 result += '#' + scope.object.ref + '#';
918 break;
919 case Debug.ScopeType.Catch:
920 result += 'Catch, ';
921 result += '#' + scope.object.ref + '#';
922 break;
923 case Debug.ScopeType.Closure:
924 result += 'Closure';
925 break;
926 default:
927 result += 'UNKNOWN';
928 }
929 return result;
930}
931
932
933// Convert a JSON response to text for display in a text based debugger.
934function DebugResponseDetails(response) {
935 details = {text:'', running:false}
936
937 try {
938 if (!response.success()) {
939 details.text = response.message();
940 return details;
941 }
942
943 // Get the running state.
944 details.running = response.running();
945
946 var body = response.body();
947 var result = '';
948 switch (response.command()) {
Steve Block6ded16b2010-05-10 14:33:55 +0100949 case 'suspend':
950 details.text = 'stopped';
951 break;
952
Steve Blocka7e24c12009-10-30 11:49:00 +0000953 case 'setbreakpoint':
954 result = 'set breakpoint #';
955 result += body.breakpoint;
956 details.text = result;
957 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100958
Steve Blocka7e24c12009-10-30 11:49:00 +0000959 case 'clearbreakpoint':
960 result = 'cleared breakpoint #';
961 result += body.breakpoint;
962 details.text = result;
963 break;
Kristian Monsen25f61362010-05-21 11:50:48 +0100964
965 case 'listbreakpoints':
966 result = 'breakpoints: (' + body.breakpoints.length + ')';
967 for (var i = 0; i < body.breakpoints.length; i++) {
968 var breakpoint = body.breakpoints[i];
969 result += '\n id=' + breakpoint.number;
970 result += ' type=' + breakpoint.type;
971 if (breakpoint.script_id) {
972 result += ' script_id=' + breakpoint.script_id;
973 }
974 if (breakpoint.script_name) {
975 result += ' script_name=' + breakpoint.script_name;
976 }
977 result += ' line=' + breakpoint.line;
978 if (breakpoint.column != null) {
979 result += ' column=' + breakpoint.column;
980 }
981 if (breakpoint.groupId) {
982 result += ' groupId=' + breakpoint.groupId;
983 }
984 if (breakpoint.ignoreCount) {
985 result += ' ignoreCount=' + breakpoint.ignoreCount;
986 }
987 if (breakpoint.active === false) {
988 result += ' inactive';
989 }
990 if (breakpoint.condition) {
991 result += ' condition=' + breakpoint.condition;
992 }
993 result += ' hit_count=' + breakpoint.hit_count;
994 }
995 details.text = result;
996 break;
Steve Block6ded16b2010-05-10 14:33:55 +0100997
Steve Blocka7e24c12009-10-30 11:49:00 +0000998 case 'backtrace':
999 if (body.totalFrames == 0) {
1000 result = '(empty stack)';
1001 } else {
1002 var result = 'Frames #' + body.fromFrame + ' to #' +
1003 (body.toFrame - 1) + ' of ' + body.totalFrames + '\n';
1004 for (i = 0; i < body.frames.length; i++) {
1005 if (i != 0) result += '\n';
1006 result += body.frames[i].text;
1007 }
1008 }
1009 details.text = result;
1010 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001011
Steve Blocka7e24c12009-10-30 11:49:00 +00001012 case 'frame':
1013 details.text = SourceUnderline(body.sourceLineText,
1014 body.column);
1015 Debug.State.currentSourceLine = body.line;
1016 Debug.State.currentFrame = body.index;
1017 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001018
Steve Blocka7e24c12009-10-30 11:49:00 +00001019 case 'scopes':
1020 if (body.totalScopes == 0) {
1021 result = '(no scopes)';
1022 } else {
1023 result = 'Scopes #' + body.fromScope + ' to #' +
1024 (body.toScope - 1) + ' of ' + body.totalScopes + '\n';
1025 for (i = 0; i < body.scopes.length; i++) {
1026 if (i != 0) {
1027 result += '\n';
1028 }
1029 result += formatScope_(body.scopes[i]);
1030 }
1031 }
1032 details.text = result;
1033 break;
1034
1035 case 'scope':
1036 result += formatScope_(body);
1037 result += '\n';
1038 var scope_object_value = response.lookup(body.object.ref);
1039 result += formatObject_(scope_object_value, true);
1040 details.text = result;
1041 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001042
Steve Blocka7e24c12009-10-30 11:49:00 +00001043 case 'evaluate':
1044 case 'lookup':
1045 if (last_cmd == 'p' || last_cmd == 'print') {
1046 result = body.text;
1047 } else {
1048 var value;
1049 if (lookup_handle) {
1050 value = response.bodyValue(lookup_handle);
1051 } else {
1052 value = response.bodyValue();
1053 }
1054 if (value.isObject()) {
1055 result += formatObject_(value, true);
1056 } else {
1057 result += 'type: ';
1058 result += value.type();
1059 if (!value.isUndefined() && !value.isNull()) {
1060 result += ', ';
1061 if (value.isString()) {
1062 result += '"';
1063 }
1064 result += value.value();
1065 if (value.isString()) {
1066 result += '"';
1067 }
1068 }
1069 result += '\n';
1070 }
1071 }
1072 details.text = result;
1073 break;
1074
1075 case 'references':
1076 var count = body.length;
1077 result += 'found ' + count + ' objects';
1078 result += '\n';
1079 for (var i = 0; i < count; i++) {
1080 var value = response.bodyValue(i);
1081 result += formatObject_(value, false);
1082 result += '\n';
1083 }
1084 details.text = result;
1085 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001086
Steve Blocka7e24c12009-10-30 11:49:00 +00001087 case 'source':
1088 // Get the source from the response.
1089 var source = body.source;
1090 var from_line = body.fromLine + 1;
1091 var lines = source.split('\n');
1092 var maxdigits = 1 + Math.floor(log10(from_line + lines.length));
1093 if (maxdigits < 3) {
1094 maxdigits = 3;
1095 }
1096 var result = '';
1097 for (var num = 0; num < lines.length; num++) {
1098 // Check if there's an extra newline at the end.
1099 if (num == (lines.length - 1) && lines[num].length == 0) {
1100 break;
1101 }
1102
1103 var current_line = from_line + num;
1104 spacer = maxdigits - (1 + Math.floor(log10(current_line)));
1105 if (current_line == Debug.State.currentSourceLine + 1) {
1106 for (var i = 0; i < maxdigits; i++) {
1107 result += '>';
1108 }
1109 result += ' ';
1110 } else {
1111 for (var i = 0; i < spacer; i++) {
1112 result += ' ';
1113 }
1114 result += current_line + ': ';
1115 }
1116 result += lines[num];
1117 result += '\n';
1118 }
1119 details.text = result;
1120 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001121
Steve Blocka7e24c12009-10-30 11:49:00 +00001122 case 'scripts':
1123 var result = '';
1124 for (i = 0; i < body.length; i++) {
1125 if (i != 0) result += '\n';
1126 if (body[i].id) {
1127 result += body[i].id;
1128 } else {
1129 result += '[no id]';
1130 }
1131 result += ', ';
1132 if (body[i].name) {
1133 result += body[i].name;
1134 } else {
1135 if (body[i].compilationType == Debug.ScriptCompilationType.Eval) {
1136 result += 'eval from ';
1137 var script_value = response.lookup(body[i].evalFromScript.ref);
1138 result += ' ' + script_value.field('name');
1139 result += ':' + (body[i].evalFromLocation.line + 1);
1140 result += ':' + body[i].evalFromLocation.column;
1141 } else if (body[i].compilationType ==
1142 Debug.ScriptCompilationType.JSON) {
1143 result += 'JSON ';
1144 } else { // body[i].compilation == Debug.ScriptCompilationType.Host
1145 result += '[unnamed] ';
1146 }
1147 }
1148 result += ' (lines: ';
1149 result += body[i].lineCount;
1150 result += ', length: ';
1151 result += body[i].sourceLength;
1152 if (body[i].type == Debug.ScriptType.Native) {
1153 result += ', native';
1154 } else if (body[i].type == Debug.ScriptType.Extension) {
1155 result += ', extension';
1156 }
1157 result += '), [';
1158 var sourceStart = body[i].sourceStart;
1159 if (sourceStart.length > 40) {
1160 sourceStart = sourceStart.substring(0, 37) + '...';
1161 }
1162 result += sourceStart;
1163 result += ']';
1164 }
1165 details.text = result;
1166 break;
1167
1168 case 'threads':
1169 var result = 'Active V8 threads: ' + body.totalThreads + '\n';
1170 body.threads.sort(function(a, b) { return a.id - b.id; });
1171 for (i = 0; i < body.threads.length; i++) {
1172 result += body.threads[i].current ? '*' : ' ';
1173 result += ' ';
1174 result += body.threads[i].id;
1175 result += '\n';
1176 }
1177 details.text = result;
1178 break;
1179
1180 case 'continue':
1181 details.text = "(running)";
1182 break;
Steve Block6ded16b2010-05-10 14:33:55 +01001183
Steve Blocka7e24c12009-10-30 11:49:00 +00001184 default:
1185 details.text =
Kristian Monsen25f61362010-05-21 11:50:48 +01001186 'Response for unknown command \'' + response.command() + '\'' +
1187 ' (' + response.raw_json() + ')';
Steve Blocka7e24c12009-10-30 11:49:00 +00001188 }
1189 } catch (e) {
1190 details.text = 'Error: "' + e + '" formatting response';
1191 }
Steve Block6ded16b2010-05-10 14:33:55 +01001192
Steve Blocka7e24c12009-10-30 11:49:00 +00001193 return details;
1194};
1195
1196
1197/**
1198 * Protocol packages send from the debugger.
1199 * @param {string} json - raw protocol packet as JSON string.
1200 * @constructor
1201 */
1202function ProtocolPackage(json) {
Kristian Monsen25f61362010-05-21 11:50:48 +01001203 this.raw_json_ = json;
Steve Blocka7e24c12009-10-30 11:49:00 +00001204 this.packet_ = JSON.parse(json);
1205 this.refs_ = [];
1206 if (this.packet_.refs) {
1207 for (var i = 0; i < this.packet_.refs.length; i++) {
1208 this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
1209 }
1210 }
1211}
1212
1213
1214/**
1215 * Get the packet type.
1216 * @return {String} the packet type
1217 */
1218ProtocolPackage.prototype.type = function() {
1219 return this.packet_.type;
1220}
1221
1222
1223/**
1224 * Get the packet event.
1225 * @return {Object} the packet event
1226 */
1227ProtocolPackage.prototype.event = function() {
1228 return this.packet_.event;
1229}
1230
1231
1232/**
1233 * Get the packet request sequence.
1234 * @return {number} the packet request sequence
1235 */
1236ProtocolPackage.prototype.requestSeq = function() {
1237 return this.packet_.request_seq;
1238}
1239
1240
1241/**
1242 * Get the packet request sequence.
1243 * @return {number} the packet request sequence
1244 */
1245ProtocolPackage.prototype.running = function() {
1246 return this.packet_.running ? true : false;
1247}
1248
1249
1250ProtocolPackage.prototype.success = function() {
1251 return this.packet_.success ? true : false;
1252}
1253
1254
1255ProtocolPackage.prototype.message = function() {
1256 return this.packet_.message;
1257}
1258
1259
1260ProtocolPackage.prototype.command = function() {
1261 return this.packet_.command;
1262}
1263
1264
1265ProtocolPackage.prototype.body = function() {
1266 return this.packet_.body;
1267}
1268
1269
1270ProtocolPackage.prototype.bodyValue = function(index) {
1271 if (index != null) {
1272 return new ProtocolValue(this.packet_.body[index], this);
1273 } else {
1274 return new ProtocolValue(this.packet_.body, this);
1275 }
1276}
1277
1278
1279ProtocolPackage.prototype.body = function() {
1280 return this.packet_.body;
1281}
1282
1283
1284ProtocolPackage.prototype.lookup = function(handle) {
1285 var value = this.refs_[handle];
1286 if (value) {
1287 return new ProtocolValue(value, this);
1288 } else {
1289 return new ProtocolReference(handle);
1290 }
1291}
1292
1293
Kristian Monsen25f61362010-05-21 11:50:48 +01001294ProtocolPackage.prototype.raw_json = function() {
1295 return this.raw_json_;
1296}
1297
1298
Steve Blocka7e24c12009-10-30 11:49:00 +00001299function ProtocolValue(value, packet) {
1300 this.value_ = value;
1301 this.packet_ = packet;
1302}
1303
1304
1305/**
1306 * Get the value type.
1307 * @return {String} the value type
1308 */
1309ProtocolValue.prototype.type = function() {
1310 return this.value_.type;
1311}
1312
1313
1314/**
Steve Block6ded16b2010-05-10 14:33:55 +01001315 * Get a metadata field from a protocol value.
Steve Blocka7e24c12009-10-30 11:49:00 +00001316 * @return {Object} the metadata field value
1317 */
1318ProtocolValue.prototype.field = function(name) {
1319 return this.value_[name];
1320}
1321
1322
1323/**
1324 * Check is the value is a primitive value.
1325 * @return {boolean} true if the value is primitive
1326 */
1327ProtocolValue.prototype.isPrimitive = function() {
1328 return this.isUndefined() || this.isNull() || this.isBoolean() ||
1329 this.isNumber() || this.isString();
1330}
1331
1332
1333/**
1334 * Get the object handle.
1335 * @return {number} the value handle
1336 */
1337ProtocolValue.prototype.handle = function() {
1338 return this.value_.handle;
1339}
1340
1341
1342/**
1343 * Check is the value is undefined.
1344 * @return {boolean} true if the value is undefined
1345 */
1346ProtocolValue.prototype.isUndefined = function() {
1347 return this.value_.type == 'undefined';
1348}
1349
1350
1351/**
1352 * Check is the value is null.
1353 * @return {boolean} true if the value is null
1354 */
1355ProtocolValue.prototype.isNull = function() {
1356 return this.value_.type == 'null';
1357}
1358
1359
1360/**
1361 * Check is the value is a boolean.
1362 * @return {boolean} true if the value is a boolean
1363 */
1364ProtocolValue.prototype.isBoolean = function() {
1365 return this.value_.type == 'boolean';
1366}
1367
1368
1369/**
1370 * Check is the value is a number.
1371 * @return {boolean} true if the value is a number
1372 */
1373ProtocolValue.prototype.isNumber = function() {
1374 return this.value_.type == 'number';
1375}
1376
1377
1378/**
1379 * Check is the value is a string.
1380 * @return {boolean} true if the value is a string
1381 */
1382ProtocolValue.prototype.isString = function() {
1383 return this.value_.type == 'string';
1384}
1385
1386
1387/**
1388 * Check is the value is an object.
1389 * @return {boolean} true if the value is an object
1390 */
1391ProtocolValue.prototype.isObject = function() {
1392 return this.value_.type == 'object' || this.value_.type == 'function' ||
1393 this.value_.type == 'error' || this.value_.type == 'regexp';
1394}
1395
1396
1397/**
1398 * Get the constructor function
1399 * @return {ProtocolValue} constructor function
1400 */
1401ProtocolValue.prototype.constructorFunctionValue = function() {
1402 var ctor = this.value_.constructorFunction;
1403 return this.packet_.lookup(ctor.ref);
1404}
1405
1406
1407/**
1408 * Get the __proto__ value
1409 * @return {ProtocolValue} __proto__ value
1410 */
1411ProtocolValue.prototype.protoObjectValue = function() {
1412 var proto = this.value_.protoObject;
1413 return this.packet_.lookup(proto.ref);
1414}
1415
1416
1417/**
1418 * Get the number og properties.
1419 * @return {number} the number of properties
1420 */
1421ProtocolValue.prototype.propertyCount = function() {
1422 return this.value_.properties ? this.value_.properties.length : 0;
1423}
1424
1425
1426/**
1427 * Get the specified property name.
1428 * @return {string} property name
1429 */
1430ProtocolValue.prototype.propertyName = function(index) {
1431 var property = this.value_.properties[index];
1432 return property.name;
1433}
1434
1435
1436/**
1437 * Return index for the property name.
1438 * @param name The property name to look for
1439 * @return {number} index for the property name
1440 */
1441ProtocolValue.prototype.propertyIndex = function(name) {
1442 for (var i = 0; i < this.propertyCount(); i++) {
1443 if (this.value_.properties[i].name == name) {
1444 return i;
1445 }
1446 }
1447 return null;
1448}
1449
1450
1451/**
1452 * Get the specified property value.
1453 * @return {ProtocolValue} property value
1454 */
1455ProtocolValue.prototype.propertyValue = function(index) {
1456 var property = this.value_.properties[index];
1457 return this.packet_.lookup(property.ref);
1458}
1459
1460
1461/**
1462 * Check is the value is a string.
1463 * @return {boolean} true if the value is a string
1464 */
1465ProtocolValue.prototype.value = function() {
1466 return this.value_.value;
1467}
1468
1469
1470function ProtocolReference(handle) {
1471 this.handle_ = handle;
1472}
1473
1474
1475ProtocolReference.prototype.handle = function() {
1476 return this.handle_;
1477}
1478
1479
1480function MakeJSONPair_(name, value) {
1481 return '"' + name + '":' + value;
1482}
1483
1484
1485function ArrayToJSONObject_(content) {
1486 return '{' + content.join(',') + '}';
1487}
1488
1489
1490function ArrayToJSONArray_(content) {
1491 return '[' + content.join(',') + ']';
1492}
1493
1494
1495function BooleanToJSON_(value) {
Steve Block6ded16b2010-05-10 14:33:55 +01001496 return String(value);
Steve Blocka7e24c12009-10-30 11:49:00 +00001497}
1498
1499
1500function NumberToJSON_(value) {
Steve Block6ded16b2010-05-10 14:33:55 +01001501 return String(value);
Steve Blocka7e24c12009-10-30 11:49:00 +00001502}
1503
1504
1505// Mapping of some control characters to avoid the \uXXXX syntax for most
1506// commonly used control cahracters.
1507const ctrlCharMap_ = {
1508 '\b': '\\b',
1509 '\t': '\\t',
1510 '\n': '\\n',
1511 '\f': '\\f',
1512 '\r': '\\r',
1513 '"' : '\\"',
1514 '\\': '\\\\'
1515};
1516
1517
1518// Regular expression testing for ", \ and control characters (0x00 - 0x1F).
1519const ctrlCharTest_ = new RegExp('["\\\\\x00-\x1F]');
1520
1521
1522// Regular expression matching ", \ and control characters (0x00 - 0x1F)
1523// globally.
1524const ctrlCharMatch_ = new RegExp('["\\\\\x00-\x1F]', 'g');
1525
1526
1527/**
1528 * Convert a String to its JSON representation (see http://www.json.org/). To
1529 * avoid depending on the String object this method calls the functions in
1530 * string.js directly and not through the value.
1531 * @param {String} value The String value to format as JSON
1532 * @return {string} JSON formatted String value
1533 */
1534function StringToJSON_(value) {
1535 // Check for" , \ and control characters (0x00 - 0x1F). No need to call
1536 // RegExpTest as ctrlchar is constructed using RegExp.
1537 if (ctrlCharTest_.test(value)) {
1538 // Replace ", \ and control characters (0x00 - 0x1F).
1539 return '"' +
1540 value.replace(ctrlCharMatch_, function (char) {
1541 // Use charmap if possible.
1542 var mapped = ctrlCharMap_[char];
1543 if (mapped) return mapped;
1544 mapped = char.charCodeAt();
1545 // Convert control character to unicode escape sequence.
1546 return '\\u00' +
1547 '0' + // TODO %NumberToRadixString(Math.floor(mapped / 16), 16) +
1548 '0' // TODO %NumberToRadixString(mapped % 16, 16);
1549 })
1550 + '"';
1551 }
1552
1553 // Simple string with no special characters.
1554 return '"' + value + '"';
1555}
1556
1557
1558/**
1559 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
1560 * this method calls the functions in date.js directly and not through the
1561 * value.
1562 * @param {Date} value The Date value to format as JSON
1563 * @return {string} JSON formatted Date value
1564 */
1565function DateToISO8601_(value) {
1566 function f(n) {
1567 return n < 10 ? '0' + n : n;
1568 }
1569 function g(n) {
1570 return n < 10 ? '00' + n : n < 100 ? '0' + n : n;
1571 }
1572 return builtins.GetUTCFullYearFrom(value) + '-' +
1573 f(builtins.GetUTCMonthFrom(value) + 1) + '-' +
1574 f(builtins.GetUTCDateFrom(value)) + 'T' +
1575 f(builtins.GetUTCHoursFrom(value)) + ':' +
1576 f(builtins.GetUTCMinutesFrom(value)) + ':' +
1577 f(builtins.GetUTCSecondsFrom(value)) + '.' +
1578 g(builtins.GetUTCMillisecondsFrom(value)) + 'Z';
1579}
1580
1581
1582/**
1583 * Convert a Date to ISO 8601 format. To avoid depending on the Date object
1584 * this method calls the functions in date.js directly and not through the
1585 * value.
1586 * @param {Date} value The Date value to format as JSON
1587 * @return {string} JSON formatted Date value
1588 */
1589function DateToJSON_(value) {
1590 return '"' + DateToISO8601_(value) + '"';
1591}
1592
1593
1594/**
1595 * Convert an Object to its JSON representation (see http://www.json.org/).
1596 * This implementation simply runs through all string property names and adds
1597 * each property to the JSON representation for some predefined types. For type
1598 * "object" the function calls itself recursively unless the object has the
1599 * function property "toJSONProtocol" in which case that is used. This is not
1600 * a general implementation but sufficient for the debugger. Note that circular
1601 * structures will cause infinite recursion.
1602 * @param {Object} object The object to format as JSON
1603 * @return {string} JSON formatted object value
1604 */
1605function SimpleObjectToJSON_(object) {
1606 var content = [];
1607 for (var key in object) {
1608 // Only consider string keys.
1609 if (typeof key == 'string') {
1610 var property_value = object[key];
1611
1612 // Format the value based on its type.
1613 var property_value_json;
1614 switch (typeof property_value) {
1615 case 'object':
1616 if (typeof property_value.toJSONProtocol == 'function') {
1617 property_value_json = property_value.toJSONProtocol(true)
1618 } else if (property_value.constructor.name == 'Array'){
1619 property_value_json = SimpleArrayToJSON_(property_value);
1620 } else {
1621 property_value_json = SimpleObjectToJSON_(property_value);
1622 }
1623 break;
1624
1625 case 'boolean':
1626 property_value_json = BooleanToJSON_(property_value);
1627 break;
1628
1629 case 'number':
1630 property_value_json = NumberToJSON_(property_value);
1631 break;
1632
1633 case 'string':
1634 property_value_json = StringToJSON_(property_value);
1635 break;
1636
1637 default:
1638 property_value_json = null;
1639 }
1640
1641 // Add the property if relevant.
1642 if (property_value_json) {
1643 content.push(StringToJSON_(key) + ':' + property_value_json);
1644 }
1645 }
1646 }
1647
1648 // Make JSON object representation.
1649 return '{' + content.join(',') + '}';
1650}
1651
1652
1653/**
1654 * Convert an array to its JSON representation. This is a VERY simple
1655 * implementation just to support what is needed for the debugger.
1656 * @param {Array} arrya The array to format as JSON
1657 * @return {string} JSON formatted array value
1658 */
1659function SimpleArrayToJSON_(array) {
1660 // Make JSON array representation.
1661 var json = '[';
1662 for (var i = 0; i < array.length; i++) {
1663 if (i != 0) {
1664 json += ',';
1665 }
1666 var elem = array[i];
1667 if (elem.toJSONProtocol) {
1668 json += elem.toJSONProtocol(true)
1669 } else if (typeof(elem) === 'object') {
1670 json += SimpleObjectToJSON_(elem);
1671 } else if (typeof(elem) === 'boolean') {
1672 json += BooleanToJSON_(elem);
1673 } else if (typeof(elem) === 'number') {
1674 json += NumberToJSON_(elem);
1675 } else if (typeof(elem) === 'string') {
1676 json += StringToJSON_(elem);
1677 } else {
1678 json += elem;
1679 }
1680 }
1681 json += ']';
1682 return json;
1683}