/** * @fileoverview Assorted helper functions * @author Jeff Parsons (@jeffpar) * @version 1.0 * Created 2014-03-09 * * Copyright © 2012-2015 Jeff Parsons * * This file is part of the JavaScript Machines Project (aka JSMachines) at * and . * * JSMachines is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with JSMachines. * If not, see . * * You are required to include the above copyright notice in every source code file of every * copy or modified version of this work, and to display that copyright notice on every screen * that loads or runs any version of this software (see Computer.sCopyright). * * Some JSMachines files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of the * JSMachines Project for purposes of the GNU General Public License, and the author does not claim * any copyright as to their contents. */ "use strict"; var usr = {}; /** * binarySearch(a, v, fnCompare) * * @param {Array} a is an array * @param {number|string|Array|Object} v * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point */ usr.binarySearch = function(a, v, fnCompare) { var left = 0; var right = a.length; var found = 0; if (fnCompare === undefined) { fnCompare = function(a, b) { return a > b? 1 : a < b? -1 : 0; }; } while (left < right) { var middle = (left + right) >> 1; var compareResult; compareResult = fnCompare(v, a[middle]); if (compareResult > 0) { left = middle + 1; } else { right = middle; found = !compareResult; } } return found? left : ~left; }; /** * binaryInsert(a, v, fnCompare) * * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the * element is inserted into the array at the appropriate index. * * @param {Array} a is an array * @param {number|string|Array|Object} v is the value to insert * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] */ usr.binaryInsert = function(a, v, fnCompare) { var index = usr.binarySearch(a, v, fnCompare); if (index < 0) { a.splice(-(index + 1), 0, v); } }; /** * getTime() * * @return {number} the current time, in milliseconds */ usr.getTime = Date.now || function() { return +new Date(); }; /** * getTimestamp() * * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") */ usr.getTimestamp = function() { var date = new Date(); var padNum = function(n) { return (n < 10? "0" : "") + n; }; return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds()); }; /** * getMonthDays(nMonth, nYear) * * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. * * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what * a real RTC actually does. * * @param {number} nMonth (1-12) * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) * @return {number} the maximum (1-based) day allowed for the specified month and year */ usr.getMonthDays = function(nMonth, nYear) { var nDays = usr.aMonthDays[nMonth - 1]; if (nDays == 28) { if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { nDays++; } } return nDays; }; usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; /** * formatDate(sFormat, date) * * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") * @param {Date} [date] (default is the current time) * @return {string} * * Supported identifiers in sFormat include: * * a: lowercase ante meridiem and post meridiem (am or pm) * d: day of the month, 2 digits with leading zeros (01,...,31) * g: hour in 12-hour format, without leading zeros (1,...,12) * i: minutes, with leading zeros (00,...,59) * j: day of the month, without leading zeros (1,...,31) * l: day of the week ("Sunday",...,"Saturday") * m: month, with leading zeros (01,...,12) * s: seconds, with leading zeros (00,...,59) * F: month ("January",...,"December") * H: hour in 24-hour format, with leading zeros (00,...,23) * Y: year (eg, 2014) * * For more inspiration, see: http://php.net/manual/en/function.date.php */ usr.formatDate = function(sFormat, date) { var sDate = ""; if (!date) date = new Date(); var iHour = date.getHours(); var iDay = date.getDate(); var iMonth = date.getMonth() + 1; for (var i = 0; i < sFormat.length; i++) { var ch; switch((ch = sFormat.charAt(i))) { case 'a': sDate += (iHour < 12? "am" : "pm"); break; case 'd': sDate += ('0' + iDay).slice(-2); break; case 'g': sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour)); break; case 'i': sDate += ('0' + date.getMinutes()).slice(-2); break; case 'j': sDate += iDay; break; case 'l': sDate += usr.asDays[date.getDay()]; break; case 'm': sDate += ('0' + iMonth).slice(-2); break; case 's': sDate += ('0' + date.getSeconds()).slice(-2); break; case 'F': sDate += usr.asMonths[iMonth - 1]; break; case 'H': sDate += ('0' + iHour).slice(-2); break; case 'Y': sDate += date.getFullYear(); break; default: sDate += ch; break; } } return sDate; }; /** * @typedef {{ * mask: number, * shift: number * }} */ var BitField; /** * @typedef {Object.} */ var BitFields; /** * defineBitFields(bfs) * * Prepares a bit field definition for use with getBitField() and setBitField(); eg: * * var bfs = usr.defineBitFields({num:20, count:8, btmod:1, type:3}); * * The above defines a set of bit fields containg four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31). * * usr.setBitField(bfs.num, n, 1); * * The above set bit field "bfs.num" in numeric variable "n" to the value 1. * * @param {Object} bfs * @return {BitFields} */ usr.defineBitFields = function(bfs) { var bit = 0; for (var f in bfs) { var width = bfs[f]; var mask = ((1 << width) - 1) << bit; bfs[f] = {mask: mask, shift: bit}; bit += width; } // Component.assert(bit <= 32); return bfs; }; /** * initBitFields(bfs, ...) * * @param {BitFields} bfs * @param {...number} var_args * @return {number} a value containing all supplied bit fields */ usr.initBitFields = function(bfs, var_args) { var v = 0, i = 1; for (var f in bfs) { if (i >= arguments.length) break; v = usr.setBitField(bfs[f], v, arguments[i++]); } return v; }; /** * getBitField(bf, v) * * @param {BitField} bf * @param {number} v is a value containing bit fields * @return {number} the value of the bit field in v defined by bf */ usr.getBitField = function(bf, v) { return (v & bf.mask) >> bf.shift; }; /** * setBitField(bf, v, n) * * @param {BitField} bf * @param {number} v is a value containing bit fields * @param {number} n is a value to store in v in the bit field defined by bf * @return {number} updated v */ usr.setBitField = function(bf, v, n) { // Component.assert(!(n & ~(bf.mask >>> bf.shift))); return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); }; /** * indexOf(a, t, i) * * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. * * @param {Array} a * @param {*} t * @param {number} [i] * @returns {number} */ usr.indexOf = function(a, t, i) { if (Array.prototype.indexOf) { return a.indexOf(t, i); } i = i || 0; if (i < 0) i += a.length; if (i < 0) i = 0; for (var n = a.length; i < n; i++) { if (i in a && a[i] === t) return i; } return -1; }; if (NODE) module.exports = usr;