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