/* -*- mode: JavaScript; c-basic-offset: 2; -*- */ /** * * @copyright Copyright 2001-2009 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. Since few runtimes would permit that, we have to play * games. We can't declare the `message` parameter as String, because * some runtimes (as3) will coerce a message to String when passing * it as an argument, making concatenation of messages fail. */ class LzDebugMessage extends LzBootstrapMessage { var objects:Array = []; /** * @param * message: initial message (either a String or an LzMessage) */ function LzDebugMessage (message=null) { if ($as3) { // Not needed for any JS2-compliant runtime } else { // FIXME: [2009-01-09 ptw] (LPP-5232) Remove when compiler bug // fixed // // prevent incorrect sharing of instance initial values in js1 // back-end this.objects = []; } super(message); } /// These methods implement the String interface (since we are not /// allowed to subclass String, apparently /** @access private */ override public function toLowerCase ():LzMessage { var msg:LzMessage = new LzMessage(this.message.toLowerCase()); msg.objects = this.objects.concat(); return msg; }; /** @access private */ override public function toUpperCase ():LzMessage { var msg:LzMessage = new LzMessage(this.message.toUpperCase()); msg.objects = this.objects.concat(); return msg; }; /** @access private */ override public function concat (...args):LzMessage { var msg:LzMessage = new LzMessage(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; } // TODO: [2005-06-23 ptw] Make these methods maintain the objects array /** @access private */ override public function slice (...args):String { return this.message.slice.apply(this.message, args); } // TODO: [2005-06-23 ptw] Make these methods maintain the objects array /** @access private */ override public function split (...args):String { return this.message.split.apply(this.message, args); } // TODO: [2005-06-23 ptw] Make these methods maintain the objects array /** @access private */ override public function substr (...args):String { return this.message.substr.apply(this.message, args); } // TODO: [2005-06-23 ptw] Make these methods maintain the objects array /** @access private */ override public function substring (...args):String { return this.message.substring.apply(this.message, args); } /// End of String interface /** * 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 [*] 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 as3, 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 ($as3) { /** @access private */ class LzMessage extends LzDebugMessage { function LzMessage (message=null) { super(message); } /** * Propagate this * @access private */ static var xmlEscape = LzBootstrapMessage.xmlEscape; } } else { /** * Replace the bootstrap message with the more full-featured debug message * * @access private */ 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 ($as3) { function LzSourceMessage (file:String=null, line:Number=0, message='') { 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='') { // 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) { // Skip backtrace() constructor frames too skip = btsl - i + 2; } else if (callee === Debug.warnInternal) { // Skip warnInternal() caller and backtrace() constructor frames too skip = btsl - i + 1 + 2; 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 [*] 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 */ public 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='') { 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='') { 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='') { 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='') { super(file, line, message); } static var format = LzSourceMessage.format; };