diff options
Diffstat (limited to 'src/library.js')
-rw-r--r-- | src/library.js | 821 |
1 files changed, 723 insertions, 98 deletions
diff --git a/src/library.js b/src/library.js index 20a163c6..e650a545 100644 --- a/src/library.js +++ b/src/library.js @@ -559,25 +559,28 @@ LibraryManager.library = { }; } var utf8 = new Runtime.UTF8Processor(); - function simpleOutput(val) { - if (val === null || val === {{{ charCode('\n') }}}) { - output.printer(output.buffer.join('')); - output.buffer = []; - } else { - output.buffer.push(utf8.processCChar(val)); - } + function createSimpleOutput() { + var fn = function (val) { + if (val === null || val === {{{ charCode('\n') }}}) { + fn.printer(fn.buffer.join('')); + fn.buffer = []; + } else { + fn.buffer.push(utf8.processCChar(val)); + } + }; + return fn; } if (!output) { stdoutOverridden = false; - output = simpleOutput; + output = createSimpleOutput(); } if (!output.printer) output.printer = Module['print']; if (!output.buffer) output.buffer = []; if (!error) { stderrOverridden = false; - error = simpleOutput; + error = createSimpleOutput(); } - if (!error.printer) error.printer = Module['print']; + if (!error.printer) error.printer = Module['printErr']; if (!error.buffer) error.buffer = []; // Create the temporary folder, if not already created @@ -1045,27 +1048,45 @@ LibraryManager.library = { mknod: function(path, mode, dev) { // int mknod(const char *path, mode_t mode, dev_t dev); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mknod.html - if (dev !== 0 || !(mode & 0xC000)) { // S_IFREG | S_IFDIR. - // Can't create devices or pipes through mknod(). + path = Pointer_stringify(path); + var fmt = (mode & {{{ cDefine('S_IFMT') }}}); + if (fmt !== {{{ cDefine('S_IFREG') }}} && fmt !== {{{ cDefine('S_IFCHR') }}} && + fmt !== {{{ cDefine('S_IFBLK') }}} && fmt !== {{{ cDefine('S_IFIFO') }}} && + fmt !== {{{ cDefine('S_IFSOCK') }}}) { + // not valid formats for mknod ___setErrNo(ERRNO_CODES.EINVAL); return -1; - } else { - var properties = {contents: [], isFolder: Boolean(mode & 0x4000)}; // S_IFDIR. - path = FS.analyzePath(Pointer_stringify(path)); - try { - FS.createObject(path.parentObject, path.name, properties, - mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. - return 0; - } catch (e) { - return -1; - } + } + if (fmt === {{{ cDefine('S_IFCHR') }}} || fmt === {{{ cDefine('S_IFBLK') }}} || + fmt === {{{ cDefine('S_IFIFO') }}} || fmt === {{{ cDefine('S_IFSOCK') }}}) { + // not supported currently + ___setErrNo(ERRNO_CODES.EPERM); + return -1; + } + path = FS.analyzePath(path); + var properties = { contents: [], isFolder: false }; // S_IFDIR. + try { + FS.createObject(path.parentObject, path.name, properties, + mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + return 0; + } catch (e) { + return -1; } }, mkdir__deps: ['mknod'], mkdir: function(path, mode) { // int mkdir(const char *path, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html - return _mknod(path, 0x4000 | (mode & 0x180), 0); // S_IFDIR, S_IRUSR | S_IWUSR. + path = Pointer_stringify(path); + path = FS.analyzePath(path); + var properties = { contents: [], isFolder: true }; + try { + FS.createObject(path.parentObject, path.name, properties, + mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR. + return 0; + } catch (e) { + return -1; + } }, mkfifo__deps: ['__setErrNo', '$ERRNO_CODES'], mkfifo: function(path, mode) { @@ -1079,10 +1100,13 @@ LibraryManager.library = { return -1; }, chmod__deps: ['$FS'], - chmod: function(path, mode) { + chmod: function(path, mode, dontResolveLastLink) { // int chmod(const char *path, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/chmod.html - var obj = FS.findObject(Pointer_stringify(path)); + // NOTE: dontResolveLastLink is a shortcut for lchmod(). It should never be + // used in client code. + path = typeof path !== 'string' ? Pointer_stringify(path) : path; + var obj = FS.findObject(path, dontResolveLastLink); if (obj === null) return -1; obj.read = mode & 0x100; // S_IRUSR. obj.write = mode & 0x80; // S_IWUSR. @@ -1093,15 +1117,16 @@ LibraryManager.library = { fchmod: function(fildes, mode) { // int fchmod(int fildes, mode_t mode); // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fchmod.html - if (!FS.streams[fildes]) { + var stream = FS.streams[fildes]; + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - var pathArray = intArrayFromString(FS.streams[fildes].path); - return _chmod(allocate(pathArray, 'i8', ALLOC_STACK), mode); } + return _chmod(stream.path, mode); + }, + lchmod: function(path, mode) { + return _chmod(path, mode, true); }, - lchmod: function() { throw 'TODO: lchmod' }, umask__deps: ['$FS'], umask: function(newMask) { @@ -1115,6 +1140,7 @@ LibraryManager.library = { }, stat64: 'stat', fstat64: 'fstat', + lstat64: 'lstat', __01fstat64_: 'fstat', __01stat64_: 'stat', __01lstat64_: 'lstat', @@ -1775,6 +1801,8 @@ LibraryManager.library = { } else if (nbyte < 0 || offset < 0) { ___setErrNo(ERRNO_CODES.EINVAL); return -1; + } else if (offset >= stream.object.contents.length) { + return 0; } else { var bytesRead = 0; while (stream.ungotten.length && nbyte > 0) { @@ -1784,6 +1812,8 @@ LibraryManager.library = { } var contents = stream.object.contents; var size = Math.min(contents.length - offset, nbyte); + assert(size >= 0); + #if USE_TYPED_ARRAYS == 2 if (contents.subarray) { // typed array HEAPU8.set(contents.subarray(offset, offset+size), buf); @@ -1851,6 +1881,7 @@ LibraryManager.library = { } else { var ungotSize = stream.ungotten.length; bytesRead = _pread(fildes, buf, nbyte, stream.position); + assert(bytesRead >= -1); if (bytesRead != -1) { stream.position += (stream.ungotten.length - ungotSize) + bytesRead; } @@ -1867,42 +1898,42 @@ LibraryManager.library = { rmdir: function(path) { // int rmdir(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/rmdir.html - path = FS.analyzePath(Pointer_stringify(path)); + path = Pointer_stringify(path); + path = FS.analyzePath(path, true); if (!path.parentExists || !path.exists) { ___setErrNo(path.error); return -1; - } else if (!path.object.write || path.isRoot) { + } else if (!path.parentObject.write) { ___setErrNo(ERRNO_CODES.EACCES); return -1; } else if (!path.object.isFolder) { ___setErrNo(ERRNO_CODES.ENOTDIR); return -1; + } else if (path.isRoot || path.path == FS.currentPath) { + ___setErrNo(ERRNO_CODES.EBUSY); + return -1; } else { for (var i in path.object.contents) { ___setErrNo(ERRNO_CODES.ENOTEMPTY); return -1; } - if (path.path == FS.currentPath) { - ___setErrNo(ERRNO_CODES.EBUSY); - return -1; - } else { - delete path.parentObject.contents[path.name]; - return 0; - } + delete path.parentObject.contents[path.name]; + return 0; } }, unlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], unlink: function(path) { // int unlink(const char *path); // http://pubs.opengroup.org/onlinepubs/000095399/functions/unlink.html - path = FS.analyzePath(Pointer_stringify(path)); + path = Pointer_stringify(path); + path = FS.analyzePath(path, true); if (!path.parentExists || !path.exists) { ___setErrNo(path.error); return -1; } else if (path.object.isFolder) { - ___setErrNo(ERRNO_CODES.EISDIR); + ___setErrNo(ERRNO_CODES.EPERM); return -1; - } else if (!path.object.write) { + } else if (!path.parentObject.write) { ___setErrNo(ERRNO_CODES.EACCES); return -1; } else { @@ -2559,15 +2590,27 @@ LibraryManager.library = { continue; } - // TODO: Support strings like "%5c" etc. - if (format[formatIndex] === '%' && format[formatIndex+1] == 'c') { - var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; - argIndex += Runtime.getAlignSize('void*', null, true); - fields++; - next = get(); - {{{ makeSetValue('argPtr', 0, 'next', 'i8') }}} - formatIndex += 2; - continue; + if (format[formatIndex] === '%') { + var nextC = format.indexOf('c', formatIndex+1); + if (nextC > 0) { + var maxx = 1; + if (nextC > formatIndex+1) { + var sub = format.substring(formatIndex+1, nextC) + maxx = parseInt(sub); + if (maxx != sub) maxx = 0; + } + if (maxx) { + var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; + argIndex += Runtime.getAlignSize('void*', null, true); + fields++; + for (var i = 0; i < maxx; i++) { + next = get(); + {{{ makeSetValue('argPtr++', 0, 'next', 'i8') }}}; + } + formatIndex += nextC - formatIndex + 1; + continue; + } + } } // remove whitespace @@ -3183,7 +3226,7 @@ LibraryManager.library = { var flush = function(filedes) { // Right now we write all data directly, except for output devices. if (FS.streams[filedes] && FS.streams[filedes].object.output) { - if (!FS.streams[filedes].isTerminal) { // don't flush terminals, it would cause a \n to also appear + if (!FS.streams[filedes].object.isTerminal) { // don't flush terminals, it would cause a \n to also appear FS.streams[filedes].object.output(null); } } @@ -4898,7 +4941,9 @@ LibraryManager.library = { isprint: function(chr) { return 0x1F < chr && chr < 0x7F; }, - isgraph: 'isprint', + isgraph: function(chr) { + return 0x20 < chr && chr < 0x7F; + }, // Lookup tables for glibc ctype implementation. __ctype_b_loc: function() { // http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib---ctype-b-loc.html @@ -5735,8 +5780,15 @@ LibraryManager.library = { rintf: 'rint', lrint: 'rint', lrintf: 'rint', +#if USE_TYPED_ARRAYS == 2 + llrint: function(x) { + x = (x < 0) ? -Math.round(-x) : Math.round(x); + {{{ makeStructuralReturn(splitI64('x')) }}}; + }, +#else llrint: 'rint', - llrintf: 'rint', +#endif + llrintf: 'llrint', nearbyint: 'rint', nearbyintf: 'rint', trunc: function(x) { @@ -5878,7 +5930,7 @@ LibraryManager.library = { dlopen: function(filename, flag) { // void *dlopen(const char *file, int mode); // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html - filename = (ENV['LD_LIBRARY_PATH'] || '/') + Pointer_stringify(filename); + filename = filename === 0 ? '__self__' : (ENV['LD_LIBRARY_PATH'] || '/') + Pointer_stringify(filename); if (DLFCN_DATA.loadedLibNames[filename]) { // Already loaded; increment ref count and return. @@ -5887,48 +5939,55 @@ LibraryManager.library = { return handle; } - var target = FS.findObject(filename); - if (!target || target.isFolder || target.isDevice) { - DLFCN_DATA.errorMsg = 'Could not find dynamic lib: ' + filename; - return 0; + if (filename === '__self__') { + var handle = -1; + var lib_module = Module; + var cached_functions = SYMBOL_TABLE; } else { - FS.forceLoadFile(target); - var lib_data = intArrayToString(target.contents); - } + var target = FS.findObject(filename); + if (!target || target.isFolder || target.isDevice) { + DLFCN_DATA.errorMsg = 'Could not find dynamic lib: ' + filename; + return 0; + } else { + FS.forceLoadFile(target); + var lib_data = intArrayToString(target.contents); + } - try { - var lib_module = eval(lib_data)({{{ Functions.getTable('x') }}}.length); - } catch (e) { + try { + var lib_module = eval(lib_data)({{{ Functions.getTable('x') }}}.length); + } catch (e) { #if ASSERTIONS - Module.printErr('Error in loading dynamic library: ' + e); + Module.printErr('Error in loading dynamic library: ' + e); #endif - DLFCN_DATA.errorMsg = 'Could not evaluate dynamic lib: ' + filename; - return 0; - } + DLFCN_DATA.errorMsg = 'Could not evaluate dynamic lib: ' + filename; + return 0; + } - // Not all browsers support Object.keys(). - var handle = 1; - for (var key in DLFCN_DATA.loadedLibs) { - if (DLFCN_DATA.loadedLibs.hasOwnProperty(key)) handle++; - } + // Not all browsers support Object.keys(). + var handle = 1; + for (var key in DLFCN_DATA.loadedLibs) { + if (DLFCN_DATA.loadedLibs.hasOwnProperty(key)) handle++; + } + + // We don't care about RTLD_NOW and RTLD_LAZY. + if (flag & 256) { // RTLD_GLOBAL + for (var ident in lib_module) { + if (lib_module.hasOwnProperty(ident)) { + Module[ident] = lib_module[ident]; + } + } + } + var cached_functions = {}; + } DLFCN_DATA.loadedLibs[handle] = { refcount: 1, name: filename, module: lib_module, - cached_functions: {} + cached_functions: cached_functions }; DLFCN_DATA.loadedLibNames[filename] = handle; - // We don't care about RTLD_NOW and RTLD_LAZY. - if (flag & 256) { // RTLD_GLOBAL - for (var ident in lib_module) { - if (lib_module.hasOwnProperty(ident)) { - Module[ident] = lib_module[ident]; - } - } - } - return handle; }, // int dlclose(void* handle); @@ -5941,7 +6000,7 @@ LibraryManager.library = { return 1; } else { var lib_record = DLFCN_DATA.loadedLibs[handle]; - if (lib_record.refcount-- == 0) { + if (--lib_record.refcount == 0) { delete DLFCN_DATA.loadedLibNames[lib_record.name]; delete DLFCN_DATA.loadedLibs[handle]; } @@ -5960,13 +6019,15 @@ LibraryManager.library = { return 0; } else { var lib = DLFCN_DATA.loadedLibs[handle]; - if (!lib.module.hasOwnProperty(symbol)) { - DLFCN_DATA.errorMsg = ('Tried to lookup unknown symbol "' + symbol + - '" in dynamic lib: ' + lib.name); - return 0; + // self-dlopen means that lib.module is not a superset of + // cached_functions, so check the latter first + if (lib.cached_functions.hasOwnProperty(symbol)) { + return lib.cached_functions[symbol]; } else { - if (lib.cached_functions.hasOwnProperty(symbol)) { - return lib.cached_functions[symbol]; + if (!lib.module.hasOwnProperty(symbol)) { + DLFCN_DATA.errorMsg = ('Tried to lookup unknown symbol "' + symbol + + '" in dynamic lib: ' + lib.name); + return 0; } else { var result = lib.module[symbol]; if (typeof result == 'function') { @@ -6207,18 +6268,582 @@ LibraryManager.library = { return -1; }, - strftime: function(s, maxsize, format, timeptr) { + _MONTH_DAYS_REGULAR: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + _MONTH_DAYS_LEAP: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + + _isLeapYear: function(year) { + return year%4 === 0 && (year%100 !== 0 || year%400 === 0); + }, + + _arraySum: function(array, index) { + var sum = 0; + for (var i = 0; i <= index; sum += array[i++]); + return sum; + }, + + _addDays__deps: ['_isLeapYear', '_MONTH_DAYS_LEAP', '_MONTH_DAYS_REGULAR'], + _addDays: function(date, days) { + var newDate = new Date(date.getTime()); + while(days > 0) { + var leap = __isLeapYear(newDate.getFullYear()); + var currentMonth = newDate.getMonth(); + var daysInCurrentMonth = (leap ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR)[currentMonth]; + + if (days > daysInCurrentMonth-newDate.getDate()) { + // we spill over to next month + days -= (daysInCurrentMonth-newDate.getDate()+1); + newDate.setDate(1); + if (currentMonth < 11) { + newDate.setMonth(currentMonth+1) + } else { + newDate.setMonth(0); + newDate.setFullYear(newDate.getFullYear()+1); + } + } else { + // we stay in current month + newDate.setDate(newDate.getDate()+days); + return newDate; + } + } + + return newDate; + }, + + strftime__deps: ['__tm_struct_layout', '_isLeapYear', '_arraySum', '_addDays', '_MONTH_DAYS_REGULAR', '_MONTH_DAYS_LEAP'], + strftime: function(s, maxsize, format, tm) { // size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr); // http://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html - // TODO: Implement. - return 0; + + var date = { + tm_sec: {{{ makeGetValue('tm', '___tm_struct_layout.tm_sec', 'i32') }}}, + tm_min: {{{ makeGetValue('tm', '___tm_struct_layout.tm_min', 'i32') }}}, + tm_hour: {{{ makeGetValue('tm', '___tm_struct_layout.tm_hour', 'i32') }}}, + tm_mday: {{{ makeGetValue('tm', '___tm_struct_layout.tm_mday', 'i32') }}}, + tm_mon: {{{ makeGetValue('tm', '___tm_struct_layout.tm_mon', 'i32') }}}, + tm_year: {{{ makeGetValue('tm', '___tm_struct_layout.tm_year', 'i32') }}}, + tm_wday: {{{ makeGetValue('tm', '___tm_struct_layout.tm_wday', 'i32') }}}, + tm_yday: {{{ makeGetValue('tm', '___tm_struct_layout.tm_yday', 'i32') }}}, + tm_isdst: {{{ makeGetValue('tm', '___tm_struct_layout.tm_isdst', 'i32') }}} + }; + + var pattern = Pointer_stringify(format); + + // expand format + var EXPANSION_RULES_1 = { + '%c': '%a %b %d %H:%M:%S %Y', // Replaced by the locale's appropriate date and time representation - e.g., Mon Aug 3 14:02:01 2013 + '%D': '%m/%d/%y', // Equivalent to %m / %d / %y + '%F': '%Y-%m-%d', // Equivalent to %Y - %m - %d + '%h': '%b', // Equivalent to %b + '%r': '%I:%M:%S %p', // Replaced by the time in a.m. and p.m. notation + '%R': '%H:%M', // Replaced by the time in 24-hour notation + '%T': '%H:%M:%S', // Replaced by the time + '%x': '%m/%d/%y', // Replaced by the locale's appropriate date representation + '%X': '%H:%M:%S', // Replaced by the locale's appropriate date representation + }; + for (var rule in EXPANSION_RULES_1) { + pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_1[rule]); + } + + var WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + var leadingSomething = function(value, digits, character) { + var str = typeof value === 'number' ? value.toString() : (value || ''); + while (str.length < digits) { + str = character[0]+str; + } + return str; + }; + + var leadingNulls = function(value, digits) { + return leadingSomething(value, digits, '0'); + }; + + var compareByDay = function(date1, date2) { + var sgn = function(value) { + return value < 0 ? -1 : (value > 0 ? 1 : 0); + }; + + var compare; + if ((compare = sgn(date1.getFullYear()-date2.getFullYear())) === 0) { + if ((compare = sgn(date1.getMonth()-date2.getMonth())) === 0) { + compare = sgn(date1.getDate()-date2.getDate()); + } + } + return compare; + }; + + var getFirstWeekStartDate = function(janFourth) { + switch (janFourth.getDay()) { + case 0: // Sunday + return new Date(janFourth.getFullYear()-1, 11, 29); + case 1: // Monday + return janFourth; + case 2: // Tuesday + return new Date(janFourth.getFullYear(), 0, 3); + case 3: // Wednesday + return new Date(janFourth.getFullYear(), 0, 2); + case 4: // Thursday + return new Date(janFourth.getFullYear(), 0, 1); + case 5: // Friday + return new Date(janFourth.getFullYear()-1, 11, 31); + case 6: // Saturday + return new Date(janFourth.getFullYear()-1, 11, 30); + } + }; + + var getWeekBasedYear = function(date) { + var thisDate = __addDays(new Date(date.tm_year+1900, 0, 1), date.tm_yday); + + var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4); + var janFourthNextYear = new Date(thisDate.getFullYear()+1, 0, 4); + + var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); + var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); + + if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) { + // this date is after the start of the first week of this year + if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) { + return thisDate.getFullYear()+1; + } else { + return thisDate.getFullYear(); + } + } else { + return thisDate.getFullYear()-1; + } + }; + + var EXPANSION_RULES_2 = { + '%a': function(date) { + return WEEKDAYS[date.tm_wday].substring(0,3); + }, + '%A': function(date) { + return WEEKDAYS[date.tm_wday]; + }, + '%b': function(date) { + return MONTHS[date.tm_mon].substring(0,3); + }, + '%B': function(date) { + return MONTHS[date.tm_mon]; + }, + '%C': function(date) { + var year = date.tm_year+1900; + return leadingNulls(Math.floor(year/100),2); + }, + '%d': function(date) { + return leadingNulls(date.tm_mday, 2); + }, + '%e': function(date) { + return leadingSomething(date.tm_mday, 2, ' '); + }, + '%g': function(date) { + // %g, %G, and %V give values according to the ISO 8601:2000 standard week-based year. + // In this system, weeks begin on a Monday and week 1 of the year is the week that includes + // January 4th, which is also the week that includes the first Thursday of the year, and + // is also the first week that contains at least four days in the year. + // If the first Monday of January is the 2nd, 3rd, or 4th, the preceding days are part of + // the last week of the preceding year; thus, for Saturday 2nd January 1999, + // %G is replaced by 1998 and %V is replaced by 53. If December 29th, 30th, + // or 31st is a Monday, it and any following days are part of week 1 of the following year. + // Thus, for Tuesday 30th December 1997, %G is replaced by 1998 and %V is replaced by 01. + + return getWeekBasedYear(date).toString().substring(2); + }, + '%G': function(date) { + return getWeekBasedYear(date); + }, + '%H': function(date) { + return leadingNulls(date.tm_hour, 2); + }, + '%I': function(date) { + return leadingNulls(date.tm_hour < 13 ? date.tm_hour : date.tm_hour-12, 2); + }, + '%j': function(date) { + // Day of the year (001-366) + return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, date.tm_mon-1), 3); + }, + '%m': function(date) { + return leadingNulls(date.tm_mon+1, 2); + }, + '%M': function(date) { + return leadingNulls(date.tm_min, 2); + }, + '%n': function() { + return '\n'; + }, + '%p': function(date) { + if (date.tm_hour > 0 && date.tm_hour < 13) { + return 'AM'; + } else { + return 'PM'; + } + }, + '%S': function(date) { + return leadingNulls(date.tm_sec, 2); + }, + '%t': function() { + return '\t'; + }, + '%u': function(date) { + var day = new Date(date.tm_year+1900, date.tm_mon+1, date.tm_mday, 0, 0, 0, 0); + return day.getDay() || 7; + }, + '%U': function(date) { + // Replaced by the week number of the year as a decimal number [00,53]. + // The first Sunday of January is the first day of week 1; + // days in the new year before this are in week 0. [ tm_year, tm_wday, tm_yday] + var janFirst = new Date(date.tm_year+1900, 0, 1); + var firstSunday = janFirst.getDay() === 0 ? janFirst : __addDays(janFirst, 7-janFirst.getDay()); + var endDate = new Date(date.tm_year+1900, date.tm_mon, date.tm_mday); + + // is target date after the first Sunday? + if (compareByDay(firstSunday, endDate) < 0) { + // calculate difference in days between first Sunday and endDate + var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth()-1)-31; + var firstSundayUntilEndJanuary = 31-firstSunday.getDate(); + var days = firstSundayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate(); + return leadingNulls(Math.ceil(days/7), 2); + } + + return compareByDay(firstSunday, janFirst) === 0 ? '01': '00'; + }, + '%V': function(date) { + // Replaced by the week number of the year (Monday as the first day of the week) + // as a decimal number [01,53]. If the week containing 1 January has four + // or more days in the new year, then it is considered week 1. + // Otherwise, it is the last week of the previous year, and the next week is week 1. + // Both January 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday] + var janFourthThisYear = new Date(date.tm_year+1900, 0, 4); + var janFourthNextYear = new Date(date.tm_year+1901, 0, 4); + + var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); + var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); + + var endDate = __addDays(new Date(date.tm_year+1900, 0, 1), date.tm_yday); + + if (compareByDay(endDate, firstWeekStartThisYear) < 0) { + // if given date is before this years first week, then it belongs to the 53rd week of last year + return '53'; + } + + if (compareByDay(firstWeekStartNextYear, endDate) <= 0) { + // if given date is after next years first week, then it belongs to the 01th week of next year + return '01'; + } + + // given date is in between CW 01..53 of this calendar year + var daysDifference; + if (firstWeekStartThisYear.getFullYear() < date.tm_year+1900) { + // first CW of this year starts last year + daysDifference = date.tm_yday+32-firstWeekStartThisYear.getDate() + } else { + // first CW of this year starts this year + daysDifference = date.tm_yday+1-firstWeekStartThisYear.getDate(); + } + return leadingNulls(Math.ceil(daysDifference/7), 2); + }, + '%w': function(date) { + var day = new Date(date.tm_year+1900, date.tm_mon+1, date.tm_mday, 0, 0, 0, 0); + return day.getDay(); + }, + '%W': function(date) { + // Replaced by the week number of the year as a decimal number [00,53]. + // The first Monday of January is the first day of week 1; + // days in the new year before this are in week 0. [ tm_year, tm_wday, tm_yday] + var janFirst = new Date(date.tm_year, 0, 1); + var firstMonday = janFirst.getDay() === 1 ? janFirst : __addDays(janFirst, janFirst.getDay() === 0 ? 1 : 7-janFirst.getDay()+1); + var endDate = new Date(date.tm_year+1900, date.tm_mon, date.tm_mday); + + // is target date after the first Monday? + if (compareByDay(firstMonday, endDate) < 0) { + var februaryFirstUntilEndMonth = __arraySum(__isLeapYear(endDate.getFullYear()) ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, endDate.getMonth()-1)-31; + var firstMondayUntilEndJanuary = 31-firstMonday.getDate(); + var days = firstMondayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate(); + return leadingNulls(Math.ceil(days/7), 2); + } + return compareByDay(firstMonday, janFirst) === 0 ? '01': '00'; + }, + '%y': function(date) { + // Replaced by the last two digits of the year as a decimal number [00,99]. [ tm_year] + return (date.tm_year+1900).toString().substring(2); + }, + '%Y': function(date) { + // Replaced by the year as a decimal number (for example, 1997). [ tm_year] + return date.tm_year+1900; + }, + '%z': function(date) { + // Replaced by the offset from UTC in the ISO 8601:2000 standard format ( +hhmm or -hhmm ), + // or by no characters if no timezone is determinable. + // For example, "-0430" means 4 hours 30 minutes behind UTC (west of Greenwich). + // If tm_isdst is zero, the standard time offset is used. + // If tm_isdst is greater than zero, the daylight savings time offset is used. + // If tm_isdst is negative, no characters are returned. + // FIXME: we cannot determine time zone (or can we?) + return ''; + }, + '%Z': function(date) { + // Replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. [ tm_isdst] + // FIXME: we cannot determine time zone (or can we?) + return ''; + }, + '%%': function() { + return '%'; + } + }; + for (var rule in EXPANSION_RULES_2) { + if (pattern.indexOf(rule) >= 0) { + pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_2[rule](date)); + } + } + + var bytes = intArrayFromString(pattern, false); + if (bytes.length > maxsize) { + return 0; + } + + writeArrayToMemory(bytes, s); + return bytes.length-1; }, strftime_l: 'strftime', // no locale support yet + strptime__deps: ['__tm_struct_layout', '_isLeapYear', '_arraySum', '_addDays', '_MONTH_DAYS_REGULAR', '_MONTH_DAYS_LEAP'], strptime: function(buf, format, tm) { // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html - // TODO: Implement. + var pattern = Pointer_stringify(format); + + // escape special characters + // TODO: not sure we really need to escape all of these in JS regexps + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i=0, ii=SPECIAL_CHARS.length; i<ii; ++i) { + pattern = pattern.replace(new RegExp('\\'+SPECIAL_CHARS[i], 'g'), '\\'+SPECIAL_CHARS[i]); + } + + // reduce number of matchers + var EQUIVALENT_MATCHERS = { + '%A': '%a', + '%B': '%b', + '%c': '%x\\s+%X', + '%D': '%m\\/%d\\/%y', + '%e': '%d', + '%h': '%b', + '%R': '%H\\:%M', + '%r': '%I\\:%M\\:%S\\s%p', + '%T': '%H\\:%M\\:%S', + '%x': '%m\\/%d\\/(?:%y|%Y)', + '%X': '%H\\:%M\\:%S' + }; + for (var matcher in EQUIVALENT_MATCHERS) { + pattern = pattern.replace(matcher, EQUIVALENT_MATCHERS[matcher]); + } + + // TODO: take care of locale + + var DATE_PATTERNS = { + /* weeday name */ '%a': '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + /* month name */ '%b': '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + /* century */ '%C': '\\d\\d', + /* day of month */ '%d': '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + /* hour (24hr) */ '%H': '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + /* hour (12hr) */ '%I': '\\d(?!\\d)|0\\d|10|11|12', + /* day of year */ '%j': '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + /* month */ '%m': '0[1-9]|[1-9](?!\\d)|10|11|12', + /* minutes */ '%M': '0\\d|\\d(?!\\d)|[1-5]\\d', + /* whitespace */ '%n': '\\s', + /* AM/PM */ '%p': 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + /* seconds */ '%S': '0\\d|\\d(?!\\d)|[1-5]\\d|60', + /* week number */ '%U': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* week number */ '%W': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* weekday number */ '%w': '[0-6]', + /* 2-digit year */ '%y': '\\d\\d', + /* 4-digit year */ '%Y': '\\d\\d\\d\\d', + /* % */ '%%': '%', + /* whitespace */ '%t': '\\s', + }; + + var MONTH_NUMBERS = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; + var DAY_NUMBERS_SUN_FIRST = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; + var DAY_NUMBERS_MON_FIRST = {MON: 0, TUE: 1, WED: 2, THU: 3, FRI: 4, SAT: 5, SUN: 6}; + + for (var datePattern in DATE_PATTERNS) { + pattern = pattern.replace(datePattern, '('+datePattern+DATE_PATTERNS[datePattern]+')'); + } + + // take care of capturing groups + var capture = []; + for (var i=pattern.indexOf('%'); i>=0; i=pattern.indexOf('%')) { + capture.push(pattern[i+1]); + pattern = pattern.replace(new RegExp('\\%'+pattern[i+1], 'g'), ''); + } + + var matches = new RegExp('^'+pattern).exec(Pointer_stringify(buf)) + // Module['print'](Pointer_stringify(buf)+ ' is matched by '+((new RegExp('^'+pattern)).source)+' into: '+JSON.stringify(matches)); + + var initDate = function() { + var fixup = function(value, min, max) { + return (typeof value !== 'number' || isNaN(value)) ? min : (value>=min ? (value<=max ? value: max): min); + }; + return { + year: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_year', 'i32', 0, 0, 1) }}} + 1900 , 1970, 9999), + month: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mon', 'i32', 0, 0, 1) }}}, 0, 11), + day: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mday', 'i32', 0, 0, 1) }}}, 1, 31), + hour: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_hour', 'i32', 0, 0, 1) }}}, 0, 23), + min: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_min', 'i32', 0, 0, 1) }}}, 0, 59), + sec: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_sec', 'i32', 0, 0, 1) }}}, 0, 59) + }; + }; + + if (matches) { + var date = initDate(); + var value; + + var getMatch = function(symbol) { + var pos = capture.indexOf(symbol); + // check if symbol appears in regexp + if (pos >= 0) { + // return matched value or null (falsy!) for non-matches + return matches[pos+1]; + } + return; + } + + // seconds + if ((value=getMatch('S'))) { + date.sec = parseInt(value); + } + + // minutes + if ((value=getMatch('M'))) { + date.min = parseInt(value); + } + + // hours + if ((value=getMatch('H'))) { + // 24h clock + date.hour = parseInt(value); + } else if ((value = getMatch('I'))) { + // AM/PM clock + var hour = parseInt(value); + if ((value=getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + + // year + if ((value=getMatch('Y'))) { + // parse from four-digit year + date.year = parseInt(value); + } else if ((value=getMatch('y'))) { + // parse from two-digit year... + var year = parseInt(value); + if ((value=getMatch('C'))) { + // ...and century + year += parseInt(value)*100; + } else { + // ...and rule-of-thumb + year += year<69 ? 2000 : 1900; + } + date.year = year; + } + + // month + if ((value=getMatch('m'))) { + // parse from month number + date.month = parseInt(value)-1; + } else if ((value=getMatch('b'))) { + // parse from month name + date.month = MONTH_NUMBERS[value.substring(0,3).toUpperCase()] || 0; + // TODO: derive month from day in year+year, week number+day of week+year + } + + // day + if ((value=getMatch('d'))) { + // get day of month directly + date.day = parseInt(value); + } else if ((value=getMatch('j'))) { + // get day of month from day of year ... + var day = parseInt(value); + var leapYear = __isLeapYear(date.year); + for (var month=0; month<12; ++month) { + var daysUntilMonth = __arraySum(leapYear ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR, month-1); + if (day<=daysUntilMonth+(leapYear ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR)[month]) { + date.day = day-daysUntilMonth; + } + } + } else if ((value=getMatch('a'))) { + // get day of month from weekday ... + var weekDay = value.substring(0,3).toUpperCase(); + if ((value=getMatch('U'))) { + // ... and week number (Sunday being first day of week) + // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Sunday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = parseInt(value); + + // January 1st + var |