| '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 |
| } |