parse.js
  1  'use strict'
  2  
  3  // The ABNF grammar in the spec is totally ambiguous.
  4  //
  5  // This parser follows the operator precedence defined in the
  6  // `Order of Precedence and Parentheses` section.
  7  
  8  module.exports = function (tokens) {
  9    var index = 0
 10  
 11    function hasMore () {
 12      return index < tokens.length
 13    }
 14  
 15    function token () {
 16      return hasMore() ? tokens[index] : null
 17    }
 18  
 19    function next () {
 20      if (!hasMore()) {
 21        throw new Error()
 22      }
 23      index++
 24    }
 25  
 26    function parseOperator (operator) {
 27      var t = token()
 28      if (t && t.type === 'OPERATOR' && operator === t.string) {
 29        next()
 30        return t.string
 31      }
 32    }
 33  
 34    function parseWith () {
 35      if (parseOperator('WITH')) {
 36        var t = token()
 37        if (t && t.type === 'EXCEPTION') {
 38          next()
 39          return t.string
 40        }
 41        throw new Error('Expected exception after `WITH`')
 42      }
 43    }
 44  
 45    function parseLicenseRef () {
 46      // TODO: Actually, everything is concatenated into one string
 47      // for backward-compatibility but it could be better to return
 48      // a nice structure.
 49      var begin = index
 50      var string = ''
 51      var t = token()
 52      if (t.type === 'DOCUMENTREF') {
 53        next()
 54        string += 'DocumentRef-' + t.string + ':'
 55        if (!parseOperator(':')) {
 56          throw new Error('Expected `:` after `DocumentRef-...`')
 57        }
 58      }
 59      t = token()
 60      if (t.type === 'LICENSEREF') {
 61        next()
 62        string += 'LicenseRef-' + t.string
 63        return { license: string }
 64      }
 65      index = begin
 66    }
 67  
 68    function parseLicense () {
 69      var t = token()
 70      if (t && t.type === 'LICENSE') {
 71        next()
 72        var node = { license: t.string }
 73        if (parseOperator('+')) {
 74          node.plus = true
 75        }
 76        var exception = parseWith()
 77        if (exception) {
 78          node.exception = exception
 79        }
 80        return node
 81      }
 82    }
 83  
 84    function parseParenthesizedExpression () {
 85      var left = parseOperator('(')
 86      if (!left) {
 87        return
 88      }
 89  
 90      var expr = parseExpression()
 91  
 92      if (!parseOperator(')')) {
 93        throw new Error('Expected `)`')
 94      }
 95  
 96      return expr
 97    }
 98  
 99    function parseAtom () {
100      return (
101        parseParenthesizedExpression() ||
102        parseLicenseRef() ||
103        parseLicense()
104      )
105    }
106  
107    function makeBinaryOpParser (operator, nextParser) {
108      return function parseBinaryOp () {
109        var left = nextParser()
110        if (!left) {
111          return
112        }
113  
114        if (!parseOperator(operator)) {
115          return left
116        }
117  
118        var right = parseBinaryOp()
119        if (!right) {
120          throw new Error('Expected expression')
121        }
122        return {
123          left: left,
124          conjunction: operator.toLowerCase(),
125          right: right
126        }
127      }
128    }
129  
130    var parseAnd = makeBinaryOpParser('AND', parseAtom)
131    var parseExpression = makeBinaryOpParser('OR', parseAnd)
132  
133    var node = parseExpression()
134    if (!node || hasMore()) {
135      throw new Error('Syntax error')
136    }
137    return node
138  }