139 lines
2.8 KiB
JavaScript
139 lines
2.8 KiB
JavaScript
|
'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
|
||
|
}
|