/*jslint browser: true, undef: false, unparam: true, sub: true, vars: true, white: true, plusplus: true, maxerr: 50, indent: 4 */ // Formatting library. // Produces string and DOM representations of values. // /*global $*/ (function(baselib, $) { 'use strict'; var exports = {}; baselib.format = exports; var replaceUnprintableStringChars = function(s) { var ret = [], i; for (i = 0; i < s.length; i++) { var val = s.charCodeAt(i); switch(val) { case 7: ret.push('\\a'); break; case 8: ret.push('\\b'); break; case 9: ret.push('\\t'); break; case 10: ret.push('\\n'); break; case 11: ret.push('\\v'); break; case 12: ret.push('\\f'); break; case 13: ret.push('\\r'); break; case 34: ret.push('\\"'); break; case 92: ret.push('\\\\'); break; default: if (val >= 32 && val <= 126) { ret.push( s.charAt(i) ); } else { var numStr = val.toString(16).toUpperCase(); while (numStr.length < 4) { numStr = '0' + numStr; } ret.push('\\u' + numStr); } break; } } return ret.join(''); }; var escapeString = function(s) { return '"' + replaceUnprintableStringChars(s) + '"'; }; // toWrittenString: Any Hashtable -> String var toWrittenString = function(x, cache) { if (! cache) { cache = baselib.hashes.makeLowLevelEqHash(); } if (x === null) { return "null"; } if (x === true) { return "true"; } if (x === false) { return "false"; } if (typeof(x) === 'object') { if (cache.containsKey(x)) { return "..."; } } if (x === undefined) { return "#"; } if (typeof(x) === 'string') { return escapeString(x.toString()); } if (baselib.functions.isProcedure(x)) { return '#'; } if (typeof(x) !== 'object' && typeof(x) !== 'function') { return x.toString(); } var returnVal; if (x.toWrittenString) { returnVal = x.toWrittenString(cache); } else if (x.toDisplayedString) { returnVal = x.toDisplayedString(cache); } else { returnVal = x.toString(); } return returnVal; }; // toDisplayedString: Any Hashtable -> String var toDisplayedString = function(x, cache) { if (! cache) { cache = baselib.hashes.makeLowLevelEqHash(); } if (x === null) { return "null"; } if (x === true) { return "true"; } if (x === false) { return "false"; } if (typeof(x) === 'object') { if (cache.containsKey(x)) { return "..."; } } if (x === undefined || x === null) { return "#"; } if (typeof(x) === 'string') { return x; } if (baselib.functions.isProcedure(x)) { return '#'; } if (typeof(x) !== 'object' && typeof(x) !== 'function') { return x.toString(); } var returnVal; if (x.toDisplayedString) { returnVal = x.toDisplayedString(cache); } else if (x.toWrittenString) { returnVal = x.toWrittenString(cache); } else { returnVal = x.toString(); } return returnVal; }; var formatRegexp1 = new RegExp('~[sSaA]', 'g'); var formatRegexp2 = new RegExp("~[sSaAnevE%~]", "g"); // format: string [X ...] string -> string // String formatting. If an exception occurs, throws // a plain Error whose message describes the formatting error. var format = function(formatStr, args, functionName) { var throwFormatError = function() { functionName = functionName || 'format'; var matches = formatStr.match(formatRegexp1); var expectedNumberOfArgs = (matches === null ? 0 : matches.length); var errorStrBuffer = [functionName + ': format string requires ' + expectedNumberOfArgs + ' arguments, given ' + args.length + '; arguments were:', toWrittenString(formatStr)]; var i; for (i = 0; i < args.length; i++) { errorStrBuffer.push( toWrittenString(args[i]) ); } throw new Error(errorStrBuffer.join(' ')); }; var buffer = args.slice(0); var onTemplate = function(s) { if (s === "~~") { return "~"; } else if (s === '~n' || s === '~%') { return "\n"; } else if (s === '~s' || s === "~S") { if (buffer.length === 0) { throwFormatError(); } return toWrittenString(buffer.shift()); } else if (s === '~e' || s === "~E") { // FIXME: we don't yet have support for the error-print // handler, and currently treat ~e just like ~s. if (buffer.length === 0) { throwFormatError(); } return toWrittenString(buffer.shift()); } else if (s === '~v') { if (buffer.length === 0) { throwFormatError(); } // fprintf must do something more interesting here by // printing the dom representation directly... return toWrittenString(buffer.shift()); } else if (s === '~a' || s === "~A") { if (buffer.length === 0) { throwFormatError(); } return toDisplayedString(buffer.shift()); } else { throw new Error(functionName + ': string.replace matched invalid regexp'); } }; var result = formatStr.replace(formatRegexp2, onTemplate); if (buffer.length > 0) { throwFormatError(); } return result; }; var ToDomNodeParameters = function(params) { if (! params) { params = {}; } var k; for (k in params) { if (params.hasOwnProperty(k)) { this[k] = params[k]; } } if (this.cache === undefined) { this.cache = baselib.hashes.makeLowLevelEqHash(); } if (this.cycles === undefined) { this.cycles = baselib.hashes.makeLowLevelEqHash(); } if (this.depth === undefined) { this.depth = 0; } if (this.objectCounter === undefined) { this.objectCounter = 0; } }; ToDomNodeParameters.prototype.incrementDepth = function() { return new ToDomNodeParameters({ mode : this.mode, depth: this.depth + 1, cache: this.cache, cycles: this.cycles, objectCounter: this.objectCounter }); }; // getMode: -> (U "print" "display" "write" "constructor") ToDomNodeParameters.prototype.getMode = function() { if (this.mode) { return this.mode; } return 'print'; }; ToDomNodeParameters.prototype.getDepth = function(x) { return this.depth; }; ToDomNodeParameters.prototype.containsKey = function(x) { return this.cache.containsKey(x); }; ToDomNodeParameters.prototype.seesOldCycle = function(x) { return this.cycles.containsKey(x); }; ToDomNodeParameters.prototype.get = function(x) { return this.cache.get(x); }; ToDomNodeParameters.prototype.remove = function(x) { return this.cache.remove(x); }; ToDomNodeParameters.prototype.put = function(x, v) { return this.cache.put(x, v); }; ToDomNodeParameters.prototype.recur = function(x) { return toDomNode(x, this.incrementDepth()); }; // rationalToDomNode: rational -> dom-node var rationalToDomNode = function(n) { var repeatingDecimalNode = document.createElement("span"); var chunks = baselib.numbers.toRepeatingDecimal(baselib.numbers.numerator(n), baselib.numbers.denominator(n), {limit: 25}); repeatingDecimalNode.appendChild(document.createTextNode(chunks[0] + '.')); repeatingDecimalNode.appendChild(document.createTextNode(chunks[1])); if (chunks[2] === '...') { repeatingDecimalNode.appendChild( document.createTextNode(chunks[2])); } else if (chunks[2] !== '0') { var overlineSpan = document.createElement("span"); overlineSpan.style.textDecoration = 'overline'; overlineSpan.appendChild(document.createTextNode(chunks[2])); repeatingDecimalNode.appendChild(overlineSpan); } var fractionalNode = document.createElement("span"); var numeratorNode = document.createElement("sup"); numeratorNode.appendChild(document.createTextNode(String(baselib.numbers.numerator(n)))); var denominatorNode = document.createElement("sub"); denominatorNode.appendChild(document.createTextNode(String(baselib.numbers.denominator(n)))); fractionalNode.appendChild(numeratorNode); fractionalNode.appendChild(document.createTextNode("/")); fractionalNode.appendChild(denominatorNode); var numberNode = document.createElement("span"); numberNode.appendChild(repeatingDecimalNode); numberNode.appendChild(fractionalNode); fractionalNode.style['display'] = 'none'; var showingRepeating = true; numberNode.onclick = function(e) { showingRepeating = !showingRepeating; repeatingDecimalNode.style['display'] = (showingRepeating ? 'inline' : 'none'); fractionalNode.style['display'] = (!showingRepeating ? 'inline' : 'none'); }; numberNode.style['cursor'] = 'pointer'; return numberNode; }; // numberToDomNode: jsnum -> dom // Given a jsnum, produces a dom-node representation. var numberToDomNode = function(n, params) { var node; if (baselib.numbers.isExact(n)) { if (baselib.numbers.isInteger(n)) { node = document.createElement("span"); node.appendChild(document.createTextNode(n.toString())); return node; } else if (baselib.numbers.isRational(n)) { return rationalToDomNode(n); } else if (baselib.numbers.isComplex(n)) { node = document.createElement("span"); node.appendChild(document.createTextNode(n.toString())); return node; } else { node = document.createElement("span"); node.appendChild(document.createTextNode(n.toString())); return node; } } else { node = document.createElement("span"); node.appendChild(document.createTextNode(n.toString())); return node; } }; var coerseToParams = function(params) { if (params === 'write') { params = new ToDomNodeParameters({'mode' : 'write'}); } else if (params === 'print') { params = new ToDomNodeParameters({'mode' : 'print'}); } else if (params === 'display') { params = new ToDomNodeParameters({'mode' : 'display'}); } else if (params === 'constructor') { params = new ToDomNodeParameters({'mode' : 'constructor'}); } else { params = params || new ToDomNodeParameters({'mode' : 'display'}); } return params; }; // toDomNode: scheme-value -> dom-node var toDomNode = function(x, params) { var node, retval; params = coerseToParams(params); if (x === null) { node = document.createElement("span"); node.appendChild(document.createTextNode("#")); $(node).addClass("null"); return node; } if (x === undefined) { node = document.createElement("span"); node.appendChild(document.createTextNode("#")); $(node).addClass("undefined"); return node; } if (baselib.numbers.isSchemeNumber(x)) { node = numberToDomNode(x, params); $(node).addClass("number"); return node; } if (typeof(x) === 'string') { var wrapper = document.createElement("span"); wrapper.style["white-space"] = "pre"; if (params.getMode() === 'write' || params.getMode() === 'print' || params.getMode() === 'constructor') { node = document.createTextNode(toWrittenString(x)); } else { node = document.createTextNode(toDisplayedString(x)); } wrapper.appendChild(node); $(wrapper).addClass("string"); return wrapper; } if (x === true || x === false) { node = document.createElement("span"); node.appendChild(document.createTextNode(x ? "true" : "false")); $(node).addClass("boolean"); return node; } if (baselib.functions.isProcedure(x)) { node = document.createElement("span"); node.appendChild(document.createTextNode('#')); $(node).addClass("procedure"); return node; } if (typeof(x) !== 'object') { node = document.createElement("span"); node.appendChild(document.createTextNode(x.toString())); return node; } if (x.nodeType) { return x; } // Otherwise, we know the value is an object. // If we're along a print path with a loop, we need to stop // and return the key. if (params.seesOldCycle(x)) { node = document.createElement("span"); node.appendChild(document.createTextNode("#" + params.cycles.get(x) + "#")); $(node).addClass("cycle"); return node; } // If we see a fresh cycle, register it. if (params.containsKey(x)) { $('').text('#' + params.objectCounter +'=') .prependTo(params.get(x)); params.cycles.put(x, params.objectCounter); params.objectCounter++; node = document.createElement("span"); node.appendChild(document.createTextNode("#" + params.cycles.get(x) + "#")); $(node).addClass("cycle"); return node; } node = document.createElement("span"); params.put(x, node); if (x.toDomNode) { node.appendChild(x.toDomNode(params)); } else if (params.getMode() === 'write' && x.toWrittenString) { node.appendChild(document.createTextNode( x.toWrittenString(params))); } else if (params.getMode() === 'display' && x.toDisplayedString) { node.appendChild(document.createTextNode( x.toDisplayedString(params))); } else { node.appendChild(document.createTextNode(x.toString())); } params.remove(x); return node; }; ////////////////////////////////////////////////////////////////////// exports.ToDomNodeParameters = ToDomNodeParameters; exports.format = format; exports.toWrittenString = toWrittenString; exports.toDisplayedString = toDisplayedString; exports.toDomNode = toDomNode; exports.escapeString = escapeString; }(this.plt.baselib, $));