• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8module.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}
139