/* -*- mode: JavaScript; c-basic-offset: 2; -*- */ /** * * @copyright Copyright 2001-2008 Laszlo Systems, Inc. All Rights Reserved. * Use is subject to license terms. * * @access public * @topic LZX * @subtopic Debugging */ // Messages for the LaszloDebugger /** * A message is a string with annotations for the objects represented. * * This replaces the bootstrap LzMessage class in * compiler/LzFormatter * * @access private * * @devnote A Message would like to be a subclass of string, but * mutable */ class LzDebugMessage extends LzBootstrapMessage { var objects:Array; /** * @param String message: initial message */ function LzDebugMessage (message:String=null) { // can't be in prototype as it would be shared // could be a getter to defer allocation this.objects = []; super(message); } // TODO: [2006-04-17 ptw] When javascript has getters and setters: // function get length () { return this.message.length; }; // function set length (length) { this.message.length = length; return this.length; }; // Implements String interface /** @access private */ function charAt (index) { return this.message.charAt(index); } // Implements String interface /** @access private */ function charCodeAt (index) { return this.message.charCodeAt(index); } // indexOf in LzBootstrapMessage // Implements String interface /** @access private */ override function indexOf (key) { return this.message.indexOf(key); } // Implements String interface /** @access private */ function lastIndexOf (key) { return this.message.lastIndexOf(key); } // Implements String interface /** @access private */ function toLowerCase () { var msg = new LzMessage(this.message.toLowerCase()); msg.objects = this.objects.concat(); return msg; }; /** * Implements String interface * @access private */ function toUpperCase () { var msg = new LzDebugMessage(this.message.toUpperCase()); msg.objects = this.objects.concat(); return msg; }; /** * Implements String interface * @access private */ function toString () { return this.message.toString(); }; /** * Implements String interface * @access private */ function valueOf () { return this.message.valueOf(); }; /** * Implements String interface * @access private */ function concat (...args):LzDebugMessage { var msg = new LzDebugMessage(this.message.concat.apply(this.message, args)); var offset = this.message.length; for (var i = 0; i < args.length; i++) { var arg = args[i]; if (arg is LzDebugMessage) { var ao = (arg cast LzDebugMessage).objects; for (var j = 0; j < ao.length; j++) { var od = ao[j]; msg.objects.push({id: od.id, start: od.start+offset, end: od.end+offset}); } } offset += String(arg).length; } return msg; } /** * Implements String interface * @access private */ // TODO: [2005-06-23 ptw] Make these methods maintain the objects array function slice (...args):String { return this.message.slice.apply(this.message, args); } /** * Implements String interface * @access private */ // TODO: [2005-06-23 ptw] Make these methods maintain the objects array function split (...args):String { return this.message.split.apply(this.message, args); } /** * Implements String interface * @access private */ // TODO: [2005-06-23 ptw] Make these methods maintain the objects array function substr (...args):String { return this.message.substr.apply(this.message, args); } /** * Implements String interface * @access private */ // TODO: [2005-06-23 ptw] Make these methods maintain the objects array function substring (...args):String { return this.message.substring.apply(this.message, args); } /** * Appends str to the message. If obj is passed, it is recorded as an * annotation permitting the object corresponding to the string to be * recovered. * * @access private * * @param String str: the representation of an object * @param Object obj: the object represented, or null * * @devnote We use ID's rather than the actual object so that the ID * table is the only place where the debugger retains objects (which * means it can be made weak, or flushed to conserve storage). */ override function appendInternal (str:String, obj=null) { if (obj != null) { var id = Debug.IDForObject(obj); } else { var id = null; } if (id == null) { this.message += str; } else if (obj is LzDebugMessage) { // If it is already a message, just concatenate it var arg:LzDebugMessage = obj; var offset = this.message.length; this.message += arg.message; var ao = arg.objects; for (var j = 0; j < ao.length; j++) { var od = ao[j]; this.objects.push({id: od.id, start: od.start+offset, end: od.end+offset}); } } else { var start = this.message.length; this.message += str; var end = this.message.length; this.objects.push({id: id, start: start, end: end}); } this.length = this.message.length; } /** * Approximates `\+` for Messages * A string representation of each argument is appended to the * message. Objects are recorded in a fashion that will enable the * representation to be linked back to the object. * * @param Array * args: the arguments to append to the initial message * * @access private */ override function append (...args) { var len = args.length; for (var i = 0; i < len; i++) { var arg = args[i]; // annotate objects and things you have ID'd // Don't treat String's as Objects (in swf9, all strings are Strings!) if ((! ((arg is String) && (arg['constructor'] === String))) && ((arg is Object) || Debug.isObjectLike(arg) || (Debug.IDForObject(arg) != null))) { // pretty, no limit, but unique; for write-compatibility var str = Debug.__String(arg, true, Infinity, true); this.appendInternal(str, arg); } else { this.appendInternal(String(arg)); } } } /** * Convert a message to an array for passing to a remote debugger. * @param linkMaker:function(representation:String, id:Number) A * function that generates a link to the object with the id using the * representation. Defaults to just returning the object * * @access private */ function toArray (linkMaker=null):Array { if (linkMaker == null) { linkMaker = function (rep, id) {return Debug.ObjectForID(id);} } var msg = this.message; var base = 0; var limit = msg.length; var start = 0; var end = 0; var objs = this.objects var id; var array = [] var len = objs.length; for (var i = 0; i < len; i++) { var annot = objs[i]; start = annot.start; end = annot.end; id = annot.id; array.push(msg.substring(base, start).toHTML()); array.push(linkMaker(msg.substring(start,end).toHTML(), id)); base = end; } array.push(msg.substring(base, limit).toHTML()); return array; } /** * Convert a Message to HTML for display in the Debugger generating * links for each object represented. * @access private */ override function toHTML () { return this.toArray(function (...args) { return Debug.makeObjectLink.apply(Debug, args); }).join(''); }; /** * Propagate this * @access private */ static var xmlEscape = LzBootstrapMessage.xmlEscape; // Mimic built-in class, which hides all prototype methods // ASSetPropFlags(LzMessage.prototype, null, 1); }; if ($swf9) { class LzMessage extends LzDebugMessage { function LzMessage (message:String=null) { super(message); } /** * Propagate this * @access private */ static var xmlEscape = LzBootstrapMessage.xmlEscape; } } else {/** * Replace the bootstrap message with the more full-featured debug message */ var LzMessage = LzDebugMessage; } /** * A SourceMessage wraps a message with a file and line number * @param String file: filename or null * @param Number line: line number or null * @param LzMessage message: the warning message * * @see LzSourceMessage.format * @access private */ class LzSourceMessage { var file:String; var line:Number; var message:LzMessage; public var length:Number; var backtrace; static var type = ''; static var color = '#000000'; if ($swf9) { function LzSourceMessage (file:String=null, line:Number=0, message:Object='') { this.file = file; this.line = line; if (message is LzMessage) { this.message = message cast LzMessage; } else { this.message = new LzMessage(message cast String); } } } else { function LzSourceMessage (file:String=null, line:Number=0, message:String='') { // Append a backtrace if there is one -- skip back to the // $reportSourceWarning or warnInternal frames. if ('backtraceStack' in Debug) { var skip; if ((message is String) && (message['$lzsc$b'])) { // An uncaught error reported by the window.onerror handler will // have a captured stack Debug.backtraceStack = message.$lzsc$b; skip = 2; } else { var bts = Debug.backtraceStack; var btsl = bts.length; skip = 3; for (var i = btsl - 1; i > skip; i--) { var callee = bts[i].callee; if (callee === $reportSourceWarning || callee === Debug.warnInternal) { // Skip the caller and the constructor frames too skip = btsl - i + 3; break; } } } if (Debug.backtraceStack.length > skip) { this.backtrace = Debug.backtrace(skip); // Heuristicate file/line from backtrace if available if (file == null && this.backtrace) { var top = this.backtrace.userStackFrame(); if (top) { file = top.filename(); line = top.lineno(); } } } } this.file = file; this.line = line; if (message instanceof LzMessage) { this.message = message; } else { this.message = new LzMessage(message); } this.length = this.toString().length; } } /* Limit recursion */ static var level = 0; static var levelMax = 5; /** * Create a warning from a format string * @param String file: filename or null * @param Number line: line number or null * @param String control: a format control string * @param Array * args: the arguments to the format control * * @access private */ static var format = function format (file:String=null, line:Number=0, control:String='', ...args) { var debug = Debug; var message = debug.formatToString.apply(debug, [control].concat(args)); // Heuristicate file/line from args if (file == null) { for (var i = 0; i < args.length; i++) { var arg = args[i]; if (arg is LzNode) { file = arg['_dbg_filename'] || null; line = arg['_dbg_lineno'] || 0; } } } return new this(file, line, message); } /** * Get the location as a string * @access private */ function locationString (prefix:String='') { var str = prefix; if (this.file) { if (str.length) { str += ' '; } str += '@'; str += this.file; if (this.line) { str += '#'; str += this.line; } } if (str.length) { str += ': '; } return str; } /** * For console logging * @access private */ function toArray () { var array = [this.locationString(this['constructor'].type)]; if (this.message is LzMessage) { return array.concat((this.message cast LzMessage).toArray()); } return array.concat('' + this.message); } /** * Internal implementation of toString and toHTML * @access private */ function toStringInternal (conversion) { return this.locationString(this['constructor'].type) + this.message[conversion](); } /** * @access private */ var _dbg_name = function () { // Omit type from _dbg_name, since it is implicit in the class return this.locationString('') + this.message; } /** * Convert a SourceMessage to a String * @access private */ // TODO: [2005-05-08 ptw] (LPP-5934) When toString is declared // public, remove the swf9 special-case if ($swf9) { prototype.toString = function () { // SourceMessages auto-append a newline (although they really just want to // prepend a fresh-line) return this.toStringInternal('toString') + '\n'; } } else { function toString () { // SourceMessages auto-append a newline (although they really just want to // prepend a fresh-line) return this.toStringInternal('toString') + '\n'; } } /** * Convert a SourceMessage to HTML * @access private */ function toHTML () { // make the entire sourceMessage object inspectable var id = Debug.IDForObject(this); // SourceMessages auto-append a newline (although they really just want to // prepend a fresh-line) return Debug.makeObjectLink(this.toStringInternal('toHTML'), id, this['constructor']) + '\n'; } }; /** * A Warning is a sourceMessage with the tag 'WARN' * @access private */ class LzWarning extends LzSourceMessage { static var type = 'WARNING'; static var color = '#ff9900'; function LzWarning (file:String=null, line:Number=0, message:String='') { super(file, line, message); } static var format = LzSourceMessage.format; }; /** * An Error is a sourceMessage with the tag 'ERROR' * @access private */ class LzError extends LzSourceMessage { static var type = 'ERROR'; static var color = '#ff0000'; function LzError (file:String=null, line:Number=0, message:String='') { super(file, line, message); } static var format = LzSourceMessage.format; }; /** * An Info is a sourceMessage with the tag 'INFO' * @access private */ class LzInfo extends LzSourceMessage { static var type = 'INFO'; static var color = '#0066cc'; function LzInfo (file:String=null, line:Number=0, message:String='') { super(file, line, message); } static var format = LzSourceMessage.format; }; /** * A Debug is a sourceMessage with the tag 'DEBUG' * @access private */ class LzDebug extends LzSourceMessage { static var type = 'DEBUG'; static var color = '#00cc00'; function LzDebug (file:String=null, line:Number=0, message:String='') { super(file, line, message); } static var format = LzSourceMessage.format; };