function parseAbbreviation(abbr) {
		abbr = require('utils').trim(abbr);
		
		var root = new AbbreviationNode;
		var context = root.addChild(), ch;
		
		/** @type StringStream */
		var stream = require('stringStream').create(abbr);
		var loopProtector = 1000, multiplier;
		
		while (!stream.eol() && --loopProtector > 0) {
			ch = stream.peek();
			
			switch (ch) {
				case '(': // abbreviation group
					stream.start = stream.pos;
					if (stream.skipToPair('(', ')')) {
						var inner = parseAbbreviation(stripped(stream.current()));
						if (multiplier = stream.match(/^\*(\d+)?/, true)) {
							context._setRepeat(multiplier[1]);
						}
						
						_.each(inner.children, function(child) {
							context.addChild(child);
						});
					} else {
						throw 'Invalid abbreviation: mo matching ")" found for character at ' + stream.pos;
					}
					break;
					
				case '>': // child operator
					context = context.addChild();
					stream.next();
					break;
					
				case '+': // sibling operator
					context = context.parent.addChild();
					stream.next();
					break;
					
				case '^': // climb up operator
					var parent = context.parent || context;
					context = (parent.parent || parent).addChild();
					stream.next();
					break;
					
				default: // consume abbreviation
					stream.start = stream.pos;
					stream.eatWhile(function(c) {
						if (c == '[' || c == '{') {
							if (stream.skipToPair(c, pairs[c])) {
								stream.backUp(1);
								return true;
							}
							
							throw 'Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos;
						}
						
						if (c == '+') {
							// let's see if this is an expando marker
							stream.next();
							var isMarker = stream.eol() ||  ~'+>^*'.indexOf(stream.peek());
							stream.backUp(1);
							return isMarker;
						}
						
						return c != '(' && isAllowedChar(c);
					});
					
					context.setAbbreviation(stream.current());
					stream.start = stream.pos;
			}
		}
		
		if (loopProtector < 1)
			throw 'Endless loop detected';
		
		return root;
	}
function parseAbbreviation(abbr) {
		abbr = require('utils').trim(abbr);
		
		var root = new AbbreviationNode;
		var context = root.addChild(), ch;
		
		
		var stream = require('stringStream').create(abbr);
		var loopProtector = 1000, multiplier;
		
		while (!stream.eol() && --loopProtector > 0) {
			ch = stream.peek();
			
			switch (ch) {
				case '(': 
					stream.start = stream.pos;
					if (stream.skipToPair('(', ')')) {
						var inner = parseAbbreviation(stripped(stream.current()));
						if (multiplier = stream.match(/^\*(\d+)?/, true)) {
							context._setRepeat(multiplier[1]);
						}
						
						_.each(inner.children, function(child) {
							context.addChild(child);
						});
					} else {
						throw 'Invalid abbreviation: mo matching ")" found for character at ' + stream.pos;
					}
					break;
					
				case '>': 
					context = context.addChild();
					stream.next();
					break;
					
				case '+': 
					context = context.parent.addChild();
					stream.next();
					break;
					
				case '^': 
					var parent = context.parent || context;
					context = (parent.parent || parent).addChild();
					stream.next();
					break;
					
				default: 
					stream.start = stream.pos;
					stream.eatWhile(function(c) {
						if (c == '[' || c == '{') {
							if (stream.skipToPair(c, pairs[c])) {
								stream.backUp(1);
								return true;
							}
							
							throw 'Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos;
						}
						
						if (c == '+') {
							
							stream.next();
							var isMarker = stream.eol() ||  ~'+>^*'.indexOf(stream.peek());
							stream.backUp(1);
							return isMarker;
						}
						
						return c != '(' && isAllowedChar(c);
					});
					
					context.setAbbreviation(stream.current());
					stream.start = stream.pos;
			}
		}
		
		if (loopProtector < 1)
			throw 'Endless loop detected';
		
		return root;
	}
function parseAbbreviation ( abbr ) { abbr = require ( 'utils' ) . trim ( abbr ) ; var root = new AbbreviationNode ; var context = root . addChild ( ) , ch ; /** @type StringStream */ var stream = require ( 'stringStream' ) . create ( abbr ) ; var loopProtector = 1000 , multiplier ; while ( ! stream . eol ( ) && -- loopProtector > 0 ) { ch = stream . peek ( ) ; switch ( ch ) { case '(' : // abbreviation group stream . start = stream . pos ; if ( stream . skipToPair ( '(' , ')' ) ) { var inner = parseAbbreviation ( stripped ( stream . current ( ) ) ) ; if ( multiplier = stream . match ( / ^\*(\d+)? / , true ) ) { context . _setRepeat ( multiplier [ 1 ] ) ; } _ . each ( inner . children , function ( child ) { context . addChild ( child ) ; } ) ; } else { throw 'Invalid abbreviation: mo matching ")" found for character at ' + stream . pos ; } break ; case '>' : // child operator context = context . addChild ( ) ; stream . next ( ) ; break ; case '+' : // sibling operator context = context . parent . addChild ( ) ; stream . next ( ) ; break ; case '^' : // climb up operator var parent = context . parent || context ; context = ( parent . parent || parent ) . addChild ( ) ; stream . next ( ) ; break ; default : // consume abbreviation stream . start = stream . pos ; stream . eatWhile ( function ( c ) { if ( c == '[' || c == '{' ) { if ( stream . skipToPair ( c , pairs [ c ] ) ) { stream . backUp ( 1 ) ; return true ; } throw 'Invalid abbreviation: mo matching "' + pairs [ c ] + '" found for character at ' + stream . pos ; } if ( c == '+' ) { // let's see if this is an expando marker stream . next ( ) ; var isMarker = stream . eol ( ) || ~ '+>^*' . indexOf ( stream . peek ( ) ) ; stream . backUp ( 1 ) ; return isMarker ; } return c != '(' && isAllowedChar ( c ) ; } ) ; context . setAbbreviation ( stream . current ( ) ) ; stream . start = stream . pos ; } } if ( loopProtector < 1 ) throw 'Endless loop detected' ; return root ; }
Parses abbreviation into a tree
@param {String} abbr
@returns {AbbreviationNode}
Parses abbreviation into a tree