/********************************************************************** * * * **********************************************************************/ ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ var EMPTY = '' var NULL = 'null' var UNDEFINED = 'undefined' var NAN = 'NaN' var INFINITY = 'Infinity' var NEG_INFINITY = '-Infinity' var RECURSIVE = '' //--------------------------------------------------------------------- // XXX add support for pretty printing... // XXX need to destinguish between map key and value in path... var serialize = module.serialize = function(obj, path=[], seen=new Map()){ // recursive... var p = seen.get(obj) if(p != null){ return RECURSIVE.replace('%', serialize(p)) } // XXX functions... // XXX // atomics... if(obj === null){ return NULL } if(typeof(obj) != 'object'){ return obj === undefined ? UNDEFINED : isNaN(obj) ? NAN : obj === Infinity ? INFINITY : obj === -Infinity ? NEG_INFINITY : JSON.stringify(obj) } // objects... seen.set(obj, path) var elems = [] var pre = '' var join = ',' var post = '' if(obj instanceof Array){ pre = '[' post = ']' for(var i=0; i < obj.length; i++){ elems.push( i in obj ? serialize(obj[i], [...path, i], seen) : EMPTY) } } else if(obj instanceof Map){ pre = 'Map(' post = ')' elems = [serialize([...obj], path, seen)] } else if(obj instanceof Set){ pre = 'Set(' post = ')' elems = [serialize([...obj], path, seen)] } else { pre = '{' post = '}' for(var [k, v] of Object.entries(obj)){ elems.push(`${ JSON.stringify(k) }:${ serialize(v, [...path, k], seen) }`) } } return pre+ elems.join(join) +post } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // XXX add function support... var eJSON = module.eJSON = { chars: { '"': 'string', "'": 'string', 0: 'number', 1: 'number', 2: 'number', 3: 'number', 4: 'number', 5: 'number', 6: 'number', 7: 'number', 8: 'number', 9: 'number', '.': 'number', '-': 'number', '[': 'array', '{': 'object', }, words: { Infinity: 'number', 'Set(': 'set', 'Map(': 'map', null: null, undefined: undefined, NaN: NaN, '': 'empty', ' [value, i, line] // number: function(state, path, match, str, i, line){ // special cases.., if(match == 'Infinity'){ return [Infinity, i, line] } if(match == '-' && str.slice(i, 'Infinity'.length) == 'Infinity'){ return [-Infinity, i+'Infinity'.length, line] } // numbers... var j = i+1 while(j < str.length && (str[j] == '.' || (str[j] >= '0' && str[j] <= '9'))){ j++ } return [ str.slice(i, j)*1, j, line ] }, // XXX count \\n // XXX handle \\ string: function(state, path, match, str, i, line){ var j = i+1 while(j < str.length && str[j] != match){ // XXX handle '\\n'.,. if(str[j] == '\n'){ line++ } j++ } if(j == str.length && str[j-1] != match){ throw new SyntaxError('Unexpected end of input wile looking fot "'+ match +'".') } return [ str.slice(i+1, j), j+1, line ] }, identifier: function(state, path, match, str, i, line){ // XXX }, // XXX method or func??? // XXX interface??? // // handler(res, index, str, i, line) // -> [res, i, line] // sequence: function(state, path, str, i, line, end, handler, initial=[]){ var index = 0 while(i < str.length){ ;[i, line] = this.skipWhitespace(str, i, line) // done... if(str.slice(i, i+end.length) == end){ return [initial, i+end.length, line] } // empty... if(str[i] == ','){ index++ i++ // XXX this feels hackish -- can this be deligated to the handler??? initial instanceof Array && initial.length++ continue } if(str.slice(i, EMPTY.length) == EMPTY){ i += EMPTY.length // XXX this feels hackish -- can this be deligated to the handler??? initial instanceof Array && initial.length++ continue } // end of input... if(i >= str.length-1){ break } // value... ;[initial, i, line] = handler.call(this, initial, index, str, i, line) ;[i, line] = this.skipWhitespace(str, i, line) if(str[i] == ','){ i++ } index++ } // XXX better message -- show starting seq... throw new SyntaxError('Unexpected end of input wile looking for "'+ end +'".') }, array: function(state, path, match, str, i, line){ return this.sequence( state, path, str, i+1, line, ']', function(res, index, str, i, line){ var obj ;[obj, i, line] = this.value(state, [...path, index], str, i, line) res[index] = obj return [res, i, line] }) }, object: function(state, path, match, str, i, line){ return this.sequence( state, path, str, i+1, line, '}', function(res, index, str, i, line){ var obj, key // key... ;[key, i, line] = '\'"`'.includes(str[i]) ? this.string(state, path, str[i], str, i, line) : this.identifier(state, path, str[i], str, i, line) // ':'... ;[i, line] = this.skipWhitespace(str, i, line) if(str[i] != ':'){ // XXX throw new SyntaxError('Expected ":", got "'+ str[i] +'".') } i++ ;[i, line] = this.skipWhitespace(str, i, line) // value... ;[obj, i, line] = this.value(state, [...path, key], str, i, line) res[key] = obj return [res, i, line] }, {}) }, set: function(state, path, match, str, i, line){ i += match.length ;[res, i, line] = this.sequence( state, path, str, i+1, line, ']', function(res, index, str, i, line){ var obj ;[obj, i, line] = this.value( state, [...path, index], str, i+match.length, line) res.add(obj) return [res, i, line] }, new Set()) if(str[i] != ')'){ throw new SyntaxError('Expected ")", got "'+ str[i] +'".') } return [res, i, line] }, // XXX need to destinguish between key and value in path... map: function(state, path, match, str, i, line){ i += match.length ;[res, i, line] = this.sequence( state, path, str, i+1, line, ']', function(res, index, str, i, line){ var obj ;[[key, value], i, line] = this.value( state, // XXX need a way to index path or value... [...path, index], str, i+match.length, line) res.set(key, value) return [res, i, line] }, new Set()) if(str[i] != ')'){ throw new SyntaxError('Expected ")", got "'+ str[i] +'".') } return [res, i, line] }, // XXX should this be done inline or on a separate stage? // for inline we need these: // - all containers must be placed as soon as they are created // "return" before the items are processed... // - containers must be "filled" as the items are parsed // like .array(..) and .object(..) but not like .map(..) and .set(..) // - thread the root object... // alternatively, for a two stage process we need to: // - have a mechanism to write placeholders // - remember their positions (paths) // - build/get said path // XXX another strategy would be to have an path-object map and // directly reference that -- this would eliminate the need for // stage two... (XXX TEST) recursive: function(state, path, match, str, i, line){ return this.sequence( state, path, str, i+match.length, line, '>', function(res, index, str, i, line){ var obj ;[obj, i, line] = this.array(state, [...path, index], '[', str, i, line) var rec = state.recursive ??= [] rec.push([path, obj]) return [{}, i, line] }) }, func: function(state, path, match, str, i, line){ // XXX }, value: function(state, path, str, i=0, line=0){ ;[i, line] = this.skipWhitespace(str, i, line) // get handler... var NONE = {} var handler = NONE var match = str[i] if(match in this.chars){ handler = this.chars[match] } else { for(match in this.words){ if(match == str.slice(i, i+match.length)){ handler = this.words[match] break } } } // syntax error... if(handler === NONE){ var context = 8 var pre = Math.max(i-context, 0) var post = Math.min(i+context, str.length) throw new SyntaxError(`Unexpected: ${ str[i] } at:\n` +` ${line}: ..${ str.slice(pre, post) }..\n` +` ${ ' '.repeat( pre + ((line + ': ..').length) ) }^`) } // value... if(typeof(handler) != 'string'){ return [handler, i+match.length, line] } // func... return this[handler](state, path, match, str, i, line) }, parse: function(str){ // stage 1: build the object... var state = {} var res = this.value(state, [], str)[0] // stage 2: link the recursive structures... for(var [a, b] of state.recursive ?? []){ this.setItem(res, a, this.getItem(res, b)) } return res }, } var deserialize = module.deserialize = function(str){ return eJSON.parse(str) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // utils... // XXX see ./serialize.js var deepCopy = module.deepCopy = function(obj){ return deserialize( serialize(obj)) } /********************************************************************** * vim:set ts=4 sw=4 nowrap : */ return module })