docs: function() {
  		let obj = {}, 
  			key = null, 		// property key
  			extended = false, 	// whether the is extended Mark object or legacy JSON object
  			index = 0;
  		
  		let putText = function(text) {
  			// check preceding node
  			if (index > 0 && typeof obj[index-1] === 'string') {
  				// merge with previous text
  				obj[index-1] += text;
  			} else {
  				Object.defineProperty(obj, index, {value:text, writable:true, configurable:true}); // make content non-enumerable
  				index++;
  			}
  		},
  		parseContent = function() {
  			while (ch) {
  				if (ch === '{' || ch === '(' || ch === '[' && text[at] === '#') { // child object
  					let child = (ch === '(') ? pragma(obj) : (ch === '[' ? binary(obj):object(obj));  
  					Object.defineProperty(obj, index, {value:child, writable:true, configurable:true}); // make content non-enumerable
  					// all 4 types: Mark object, JSON object, Mark pragma, Mark binary store reference to parent 
  					child[$parent] = obj;  index++;  
  				}
  				else if (ch === '"' || ch === "'") { // text node
  					let str = string();
  					// only output non empty text
  					if (str) putText(str);
  				}
  				else if (ch === '}') { 
  					next();  obj[$length] = index;
  					return;
  				}
  				else {
  					error(UNEXPECT_CHAR + renderChar(ch));
  				}
  				white();
  			}
  			error(UNEXPECT_END);		
  		};
  		
  		next();  white();  // skip the starting '{'
  		while (ch) {
  			if (ch === '}') { // end of the object
  				next();  
  				if (extended) { obj[$length] = index; }
  				return obj;   // could be empty object
  			}
  			// scan the key
  			if (ch === '{' || ch === '(' || ch === '[') { // child object, pragma or binary
  				if (extended) {
  					parseContent();  return obj;
  				}
  				error(UNEXPECT_CHAR + "'"+ ch +"'");
  			}
  			if (ch === '"' || ch === "'") { // quoted key
  				var str = string();  white();
  				if (ch === ':') { // property or JSON object
  					key = str;
  				} else {
  					if (extended) { // already got type name
  						// only output non-empty text
  						if (str) putText(str);
  						parseContent();  return obj;
  					}
  					else if (!key) { // at the starting of the object
  						// create the object
  						obj = MARK(str, null, null);
  						extended = true;  // key = str;
  						continue;							
  					}
  					else { 
  						error(UNEXPECT_CHAR + renderChar(ch));
  					}
  				}
  			}
  			else { // if (ch==='_' || ch==='$' || 'a'<=ch && ch<='z' || 'A'<=ch && ch<='Z')
  				// Mark unquoted key, which needs to be valid JS identifier.
  				var ident = identifier();  white();
  				if (ch == ':') { // property value
  					key = ident;
  				} else {
  					if (!extended) { // start of Mark object
  						obj = MARK(ident, null, null);
  						extended = true;  // key = ident;
  						continue;
  					}
  					error(UNEXPECT_CHAR + renderChar(ch));
  				}
  			}
  			
  			// key-value pair
  			next(); // skip ':'
  			var val = value();
  			if (extended && !isNaN(key*1)) { // any numeric key is rejected for Mark object
  				error("Numeric key not allowed as Mark property name");
  			}
  			if (obj[key] && typeof obj[key] !== 'function') {
  				error("Duplicate key not allowed: " + key);
  			}
  			obj[key] = val;  white();
  			// ',' is optional in Mark
  			if (ch === ',') {
  				next();  white();
  			} 
  		}
  		error(UNEXPECT_END);
  	}