| 'use strict' | 
|   | 
| // The ABNF grammar in the spec is totally ambiguous. | 
| // | 
| // This parser follows the operator precedence defined in the | 
| // `Order of Precedence and Parentheses` section. | 
|   | 
| module.exports = function (tokens) { | 
|   var index = 0 | 
|   | 
|   function hasMore () { | 
|     return index < tokens.length | 
|   } | 
|   | 
|   function token () { | 
|     return hasMore() ? tokens[index] : null | 
|   } | 
|   | 
|   function next () { | 
|     if (!hasMore()) { | 
|       throw new Error() | 
|     } | 
|     index++ | 
|   } | 
|   | 
|   function parseOperator (operator) { | 
|     var t = token() | 
|     if (t && t.type === 'OPERATOR' && operator === t.string) { | 
|       next() | 
|       return t.string | 
|     } | 
|   } | 
|   | 
|   function parseWith () { | 
|     if (parseOperator('WITH')) { | 
|       var t = token() | 
|       if (t && t.type === 'EXCEPTION') { | 
|         next() | 
|         return t.string | 
|       } | 
|       throw new Error('Expected exception after `WITH`') | 
|     } | 
|   } | 
|   | 
|   function parseLicenseRef () { | 
|     // TODO: Actually, everything is concatenated into one string | 
|     // for backward-compatibility but it could be better to return | 
|     // a nice structure. | 
|     var begin = index | 
|     var string = '' | 
|     var t = token() | 
|     if (t.type === 'DOCUMENTREF') { | 
|       next() | 
|       string += 'DocumentRef-' + t.string + ':' | 
|       if (!parseOperator(':')) { | 
|         throw new Error('Expected `:` after `DocumentRef-...`') | 
|       } | 
|     } | 
|     t = token() | 
|     if (t.type === 'LICENSEREF') { | 
|       next() | 
|       string += 'LicenseRef-' + t.string | 
|       return { license: string } | 
|     } | 
|     index = begin | 
|   } | 
|   | 
|   function parseLicense () { | 
|     var t = token() | 
|     if (t && t.type === 'LICENSE') { | 
|       next() | 
|       var node = { license: t.string } | 
|       if (parseOperator('+')) { | 
|         node.plus = true | 
|       } | 
|       var exception = parseWith() | 
|       if (exception) { | 
|         node.exception = exception | 
|       } | 
|       return node | 
|     } | 
|   } | 
|   | 
|   function parseParenthesizedExpression () { | 
|     var left = parseOperator('(') | 
|     if (!left) { | 
|       return | 
|     } | 
|   | 
|     var expr = parseExpression() | 
|   | 
|     if (!parseOperator(')')) { | 
|       throw new Error('Expected `)`') | 
|     } | 
|   | 
|     return expr | 
|   } | 
|   | 
|   function parseAtom () { | 
|     return ( | 
|       parseParenthesizedExpression() || | 
|       parseLicenseRef() || | 
|       parseLicense() | 
|     ) | 
|   } | 
|   | 
|   function makeBinaryOpParser (operator, nextParser) { | 
|     return function parseBinaryOp () { | 
|       var left = nextParser() | 
|       if (!left) { | 
|         return | 
|       } | 
|   | 
|       if (!parseOperator(operator)) { | 
|         return left | 
|       } | 
|   | 
|       var right = parseBinaryOp() | 
|       if (!right) { | 
|         throw new Error('Expected expression') | 
|       } | 
|       return { | 
|         left: left, | 
|         conjunction: operator.toLowerCase(), | 
|         right: right | 
|       } | 
|     } | 
|   } | 
|   | 
|   var parseAnd = makeBinaryOpParser('AND', parseAtom) | 
|   var parseExpression = makeBinaryOpParser('OR', parseAnd) | 
|   | 
|   var node = parseExpression() | 
|   if (!node || hasMore()) { | 
|     throw new Error('Syntax error') | 
|   } | 
|   return node | 
| } |