Comparing version 3.0.2 to 4.0.0
{ | ||
"name": "json-e", | ||
"version": "3.0.2", | ||
"version": "4.0.0", | ||
"description": "json parameterization module inspired from json-parameterization", | ||
@@ -5,0 +5,0 @@ "main": "./src/index.js", |
@@ -639,2 +639,20 @@ * [Full documentation](https://json-e.js.org) | ||
Json-e supports short-circuit evaluation, so if in `||` left operand is true | ||
returning value will be true no matter what right operand is: | ||
```yaml | ||
context: {} | ||
template: {$eval: "true || b"} | ||
result: true | ||
``` | ||
And if in `&&` left operand is false returning value will be false no matter | ||
what right operand is: | ||
```yaml | ||
context: {} | ||
template: {$eval: "false && b"} | ||
result: false | ||
``` | ||
### Object Property Access | ||
@@ -641,0 +659,0 @@ |
@@ -1,2 +0,4 @@ | ||
var interpreter = require('./interpreter'); | ||
const {Parser} = require('./parser'); | ||
const Tokenizer = require("../src/tokenizer"); | ||
const {Interpreter} = require('./interpreter'); | ||
var fromNow = require('./from-now'); | ||
@@ -12,2 +14,6 @@ var stringify = require('json-stable-stringify-without-jsonify'); | ||
let syntaxRuleError = (token) => { | ||
return new SyntaxError(`Found ${token.value}, expected !=, &&, (, *, **, +, -, ., /, <, <=, ==, >, >=, [, in, ||`); | ||
}; | ||
function checkUndefinedProperties(template, allowed) { | ||
@@ -38,3 +44,3 @@ var unknownKeys = ''; | ||
if (remaining[offset+1] != '$') { | ||
let v = interpreter.parseUntilTerminator(remaining.slice(offset), 2, '}', context); | ||
let v = parseUntilTerminator(remaining.slice(offset + 2), '}', context); | ||
if (isArray(v.result) || isObject(v.result)) { | ||
@@ -74,3 +80,3 @@ let input = remaining.slice(offset + 2, offset + v.offset); | ||
return interpreter.parse(template['$eval'], context); | ||
return parse(template['$eval'], context); | ||
}; | ||
@@ -122,7 +128,7 @@ | ||
} | ||
if (isTruthy(interpreter.parse(template['$if'], context))) { | ||
if (isTruthy(parse(template['$if'], context))) { | ||
if(template.hasOwnProperty('$then')){ | ||
throw new TemplateError('$if Syntax error: $then: should be spelled then: (no $)') | ||
} | ||
return template.hasOwnProperty('then') ? render(template.then, context) : deleteMarker; | ||
@@ -196,3 +202,3 @@ } | ||
let object = isObject(value); | ||
if (object) { | ||
@@ -206,3 +212,3 @@ value = Object.keys(value).map(key => ({key, val: value[key]})); | ||
throw new TemplateError(`$map on objects expects each(${x}) to evaluate to an object`); | ||
} | ||
} | ||
return eachValue; | ||
@@ -231,3 +237,3 @@ }).filter(v => v !== deleteMarker); | ||
for (let condition of Object.keys(conditions).sort()) { | ||
if (isTruthy(interpreter.parse(condition, context))) { | ||
if (isTruthy(parse(condition, context))) { | ||
result.push(render(conditions[condition], context)); | ||
@@ -315,3 +321,3 @@ } | ||
contextClone[x] = value; | ||
return interpreter.parse(byExpr, contextClone); | ||
return parse(byExpr, contextClone); | ||
}; | ||
@@ -409,2 +415,51 @@ } else { | ||
let tokenizer = new Tokenizer({ | ||
ignore: '\\s+', // ignore all whitespace including \n | ||
patterns: { | ||
number: '[0-9]+(?:\\.[0-9]+)?', | ||
identifier: '[a-zA-Z_][a-zA-Z_0-9]*', | ||
string: '\'[^\']*\'|"[^"]*"', | ||
// avoid matching these as prefixes of identifiers e.g., `insinutations` | ||
true: 'true(?![a-zA-Z_0-9])', | ||
false: 'false(?![a-zA-Z_0-9])', | ||
in: 'in(?![a-zA-Z_0-9])', | ||
null: 'null(?![a-zA-Z_0-9])', | ||
}, | ||
tokens: [ | ||
'**', ...'+-*/[].(){}:,'.split(''), | ||
'>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', | ||
'true', 'false', 'in', 'null', 'number', | ||
'identifier', 'string', | ||
] | ||
}); | ||
let parse = (source, context) => { | ||
let parser = new Parser(tokenizer, source); | ||
let tree = parser.parse(); | ||
if (parser.current_token != null) { | ||
throw syntaxRuleError(parser.current_token); | ||
} | ||
let interpreter = new Interpreter(context); | ||
return interpreter.interpret(tree); | ||
}; | ||
let parseUntilTerminator = (source, terminator, context) => { | ||
let parser = new Parser(tokenizer, source); | ||
let tree = parser.parse(); | ||
let next = parser.current_token; | ||
if (!next) { | ||
// string ended without the terminator | ||
let errorLocation = source.length; | ||
throw new SyntaxError(`Found end of string, expected ${terminator}`, | ||
{start: errorLocation, end: errorLocation}); | ||
} else if (next.kind !== terminator) { | ||
throw syntaxRuleError(next); | ||
} | ||
let interpreter = new Interpreter(context); | ||
let result = interpreter.interpret(tree); | ||
return {result, offset: next.start + 2}; | ||
}; | ||
module.exports = (template, context = {}) => { | ||
@@ -411,0 +466,0 @@ let test = Object.keys(context).every(v => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v)); |
@@ -1,351 +0,280 @@ | ||
/* | ||
* Author: Jonas Finnemann Jensen | ||
* Github: https://github.com/jonasfj | ||
*/ | ||
var PrattParser = require('./prattparser'); | ||
var {isString, isNumber, isInteger, | ||
isArray, isObject, isFunction, isTruthy} = require('./type-utils'); | ||
var {InterpreterError} = require('./error'); | ||
const {isFunction, isObject, isString, isArray, isNumber, isInteger, isTruthy} = require("../src/type-utils"); | ||
const {InterpreterError} = require('./error'); | ||
let expectationError = (operator, expectation) => new InterpreterError(`${operator} expects ${expectation}`); | ||
let isEqual = (a, b) => { | ||
if (isArray(a) && isArray(b) && a.length === b.length) { | ||
for (let i = 0; i < a.length; i++) { | ||
if (!isEqual(a[i], b[i])) { return false; } | ||
class Interpreter { | ||
constructor(context) { | ||
this.context = context; | ||
} | ||
return true; | ||
} | ||
if (isFunction(a)) { | ||
return a === b; | ||
} | ||
if (isObject(a) && isObject(b)) { | ||
let keys = Object.keys(a).sort(); | ||
if (!isEqual(keys, Object.keys(b).sort())) { return false; } | ||
for (let k of keys) { | ||
if (!isEqual(a[k], b[k])) { return false; } | ||
} | ||
return true; | ||
} | ||
return a === b; | ||
}; | ||
let parseList = (ctx, separator, terminator) => { | ||
let list = []; | ||
if (!ctx.attempt(terminator)) { | ||
do { | ||
list.push(ctx.parse()); | ||
} while (ctx.attempt(separator)); | ||
ctx.require(terminator); | ||
} | ||
return list; | ||
}; | ||
let parseObject = (ctx) => { | ||
let obj = {}; | ||
if (!ctx.attempt('}')) { | ||
do { | ||
let k = ctx.require('identifier', 'string'); | ||
if (k.kind === 'string') { | ||
k.value = parseString(k.value); | ||
} | ||
ctx.require(':'); | ||
let v = ctx.parse(); | ||
obj[k.value] = v; | ||
} while (ctx.attempt(',')); | ||
ctx.require('}'); | ||
} | ||
return obj; | ||
}; | ||
let parseInterval = (left, token, ctx) => { | ||
let a = null, b = null, isInterval = false; | ||
if (ctx.attempt(':')) { | ||
a = 0; | ||
isInterval = true; | ||
} else { | ||
a = ctx.parse(); | ||
if (ctx.attempt(':')) { | ||
isInterval = true; | ||
visit(node) { | ||
let funcName = "visit_" + node.constructor.name; | ||
return this[funcName](node); | ||
} | ||
} | ||
if (isInterval && !ctx.attempt(']')) { | ||
b = ctx.parse(); | ||
ctx.require(']'); | ||
} | ||
if (!isInterval) { | ||
ctx.require(']'); | ||
} | ||
return accessProperty(left, a, b, isInterval); | ||
}; | ||
let accessProperty = (left, a, b, isInterval) => { | ||
if (isArray(left) || isString(left)) { | ||
if (isInterval) { | ||
b = b === null ? left.length : b; | ||
if (!isInteger(a) || !isInteger(b)) { | ||
throw new InterpreterError('cannot perform interval access with non-integers'); | ||
} | ||
return left.slice(a, b); | ||
visit_ASTNode(node) { | ||
let str; | ||
switch (node.token.kind) { | ||
case("number"): | ||
return +node.token.value; | ||
case("null"): | ||
return null; | ||
case("string"): | ||
str = node.token.value.slice(1, -1); | ||
return str; | ||
case("true"): | ||
return true; | ||
case("false"): | ||
return false; | ||
case("identifier"): | ||
return node.token.value; | ||
} | ||
} | ||
if (!isInteger(a)) { | ||
throw new InterpreterError('should only use integers to access arrays or strings'); | ||
} | ||
// for -ve index access | ||
a = a < 0 ? (left.length + a) % left.length : a; | ||
if (a >= left.length) { | ||
throw new InterpreterError('index out of bounds'); | ||
visit_UnaryOp(node) { | ||
let value = this.visit(node.expr); | ||
switch (node.token.kind) { | ||
case ("+"): | ||
if (!isNumber(value)) { | ||
throw expectationError('unary +', 'number'); | ||
} | ||
return +value; | ||
case ("-"): | ||
if (!isNumber(value)) { | ||
throw expectationError('unary -', 'number'); | ||
} | ||
return -value; | ||
case ("!"): | ||
return !isTruthy(value) | ||
} | ||
} | ||
return left[a]; | ||
} | ||
// if we reach here it means we are accessing property value from object | ||
if (!isObject(left)) { | ||
throw new InterpreterError('infix: "[..]" expects object, array, or string'); | ||
} | ||
visit_BinOp(node) { | ||
let left = this.visit(node.left); | ||
let right; | ||
switch (node.token.kind) { | ||
case ("||"): | ||
return isTruthy(left) || isTruthy(this.visit(node.right)); | ||
case ("&&"): | ||
return isTruthy(left) && isTruthy(this.visit(node.right)); | ||
default: | ||
right = this.visit(node.right); | ||
} | ||
if (!isString(a)) { | ||
throw new InterpreterError('object keys must be strings'); | ||
} | ||
switch (node.token.kind) { | ||
case ("+"): | ||
testMathOperands("+", left, right); | ||
return left + right; | ||
case ("-"): | ||
testMathOperands("-", left, right); | ||
return left - right; | ||
case ("/"): | ||
testMathOperands("/", left, right); | ||
return left / right; | ||
case ("*"): | ||
testMathOperands("*", left, right); | ||
return left * right; | ||
case (">"): | ||
testComparisonOperands(">", left, right); | ||
return left > right; | ||
case ("<"): | ||
testComparisonOperands("<", left, right); | ||
return left < right; | ||
case (">="): | ||
testComparisonOperands(">=", left, right); | ||
return left >= right; | ||
case ("<="): | ||
testComparisonOperands("<=", left, right); | ||
return left <= right; | ||
case ("!="): | ||
testComparisonOperands("!=", left, right); | ||
return !isEqual(left, right); | ||
case ("=="): | ||
testComparisonOperands("==", left, right); | ||
return isEqual(left, right); | ||
case ("**"): | ||
testMathOperands("**", left, right); | ||
return Math.pow(right, left); | ||
case ("."): { | ||
if (isObject(left)) { | ||
if (left.hasOwnProperty(right)) { | ||
return left[right]; | ||
} | ||
throw new InterpreterError(`object has no property "${right}"`); | ||
} | ||
throw expectationError('infix: .', 'objects'); | ||
} | ||
case ("in"): { | ||
if (isObject(right)) { | ||
if (!isString(left)) { | ||
throw expectationError('Infix: in-object', 'string on left side'); | ||
} | ||
right = Object.keys(right); | ||
} else if (isString(right)) { | ||
if (!isString(left)) { | ||
throw expectationError('Infix: in-string', 'string on left side'); | ||
} | ||
return right.indexOf(left) !== -1; | ||
} else if (!isArray(right)) { | ||
throw expectationError('Infix: in', 'Array, string, or object on right side'); | ||
} | ||
return right.some(r => isEqual(left, r)); | ||
} | ||
} | ||
} | ||
if (left.hasOwnProperty(a)) { | ||
return left[a]; | ||
} else { | ||
return null; | ||
} | ||
}; | ||
visit_List(node) { | ||
let list = []; | ||
let parseString = (str) => { | ||
return str.slice(1, -1); | ||
}; | ||
if (node.list[0] !== undefined) { | ||
node.list.forEach(function (item) { | ||
list.push(this.visit(item)) | ||
}, this); | ||
} | ||
let testComparisonOperands = (operator, left, right) => { | ||
return list | ||
} | ||
if (operator === '==' || operator === '!=') { | ||
return null; | ||
} | ||
visit_ValueAccess(node) { | ||
let array = this.visit(node.arr); | ||
let left = 0, right = null; | ||
let test = ['>=', '<=', '<', '>'].some(v => v === operator) | ||
&& (isNumber(left) && isNumber(right) || isString(left) && isString(right)); | ||
if (node.left) { | ||
left = this.visit(node.left); | ||
} | ||
if (node.right) { | ||
right = this.visit(node.right); | ||
} | ||
if (left < 0) { | ||
left = array.length + left | ||
} | ||
if (isArray(array) || isString(array)) { | ||
if (node.isInterval) { | ||
right = right === null ? array.length : right; | ||
if (right < 0) { | ||
right = array.length + right; | ||
if (right < 0) | ||
right = 0 | ||
} | ||
if (left > right) { | ||
left = right | ||
} | ||
if (!isInteger(left) || !isInteger(right)) { | ||
throw new InterpreterError('cannot perform interval access with non-integers'); | ||
} | ||
return array.slice(left, right) | ||
} | ||
if (!isInteger(left)) { | ||
throw new InterpreterError('should only use integers to access arrays or strings'); | ||
} | ||
if (left >= array.length) { | ||
throw new InterpreterError('index out of bounds'); | ||
} | ||
return array[left] | ||
} | ||
if (!isObject(array)) { | ||
throw expectationError(`infix: "[..]"`, 'object, array, or string'); | ||
} | ||
if (!test) { | ||
throw expectationError(`infix: ${operator}`, `numbers/strings ${operator} numbers/strings`); | ||
} | ||
return; | ||
}; | ||
if (!isString(left)) { | ||
throw new InterpreterError('object keys must be strings'); | ||
} | ||
let testMathOperands = (operator, left, right) => { | ||
if (operator === '+' && !(isNumber(left) && isNumber(right) || isString(left) && isString(right))) { | ||
throw expectationError('infix: +', 'numbers/strings + numbers/strings'); | ||
} | ||
if (['-', '*', '/', '**'].some(v => v === operator) && !(isNumber(left) && isNumber(right))) { | ||
throw expectationError(`infix: ${operator}`, `number ${operator} number`); | ||
} | ||
return; | ||
}; | ||
if (array.hasOwnProperty(left)) { | ||
return array[left]; | ||
} else { | ||
return null; | ||
} | ||
} | ||
let prefixRules = {}; | ||
let infixRules = {}; | ||
visit_ContextValue(node) { | ||
if (this.context.hasOwnProperty(node.token.value)) { | ||
let contextValue = this.context[node.token.value]; | ||
return contextValue | ||
} | ||
throw new InterpreterError(`unknown context value ${node.token.value}`); | ||
} | ||
// defining prefix rules | ||
prefixRules['number'] = (token, ctx) => { | ||
let v = Number(token.value); | ||
if (isNaN(v)) { | ||
throw new Error(`${token.value} should be a number`); | ||
} | ||
return v; | ||
}; | ||
visit_FunctionCall(node) { | ||
let args = []; | ||
prefixRules['!'] = (token, ctx) => { | ||
let operand = ctx.parse('unary'); | ||
return !isTruthy(operand); | ||
}; | ||
let funcName = this.visit(node.name); | ||
if (isFunction(funcName)) { | ||
node.args.forEach(function (item) { | ||
args.push(this.visit(item)) | ||
}, this); | ||
return funcName.apply(null, args); | ||
} else { | ||
throw new InterpreterError(`${funcName} is not callable`); | ||
} | ||
} | ||
prefixRules['-'] = (token, ctx) => { | ||
let v = ctx.parse('unary'); | ||
visit_Object(node) { | ||
let obj = {}; | ||
if (!isNumber(v)) { | ||
throw expectationError('unary -', 'number'); | ||
} | ||
for (let key in node.obj) { | ||
obj[key] = this.visit(node.obj[key]) | ||
} | ||
return -v; | ||
}; | ||
return obj | ||
} | ||
prefixRules['+'] = (token, ctx) => { | ||
let v = ctx.parse('unary'); | ||
interpret(tree) { | ||
return this.visit(tree); | ||
} | ||
} | ||
if (!isNumber(v)) { | ||
throw expectationError('unary +', 'number'); | ||
} | ||
return +v; | ||
}; | ||
prefixRules['identifier'] = (token, ctx) => { | ||
if (ctx.context.hasOwnProperty(token.value)) { | ||
return ctx.context[token.value]; | ||
} | ||
throw new InterpreterError(`unknown context value ${token.value}`); | ||
}; | ||
prefixRules['null'] = (token, ctx) => { | ||
return null; | ||
}; | ||
prefixRules['['] = (token, ctx) => parseList(ctx, ',', ']'); | ||
prefixRules['('] = (token, ctx) => { | ||
let v = ctx.parse(); | ||
ctx.require(')'); | ||
return v; | ||
}; | ||
prefixRules['{'] = (token, ctx) => parseObject(ctx); | ||
prefixRules['string'] = (token, ctx) => parseString(token.value); | ||
prefixRules['true'] = (token, ctx) => { | ||
if (token.value === 'true') { | ||
return true; | ||
} | ||
throw new Error('Only \'true/false\' is considered as bool'); | ||
}; | ||
prefixRules['false'] = (token, ctx) => { | ||
if (token.value === 'false') { | ||
return false; | ||
} | ||
throw new Error('Only \'true/false\' is considered as bool'); | ||
}; | ||
// infix rule definition starts here | ||
infixRules['+'] = infixRules['-'] = infixRules['*'] = infixRules['/'] | ||
= (left, token, ctx) => { | ||
let operator = token.kind; | ||
let right = ctx.parse(operator); | ||
testMathOperands(operator, left, right); | ||
switch (operator) { | ||
case '+': return left + right; | ||
case '-': return left - right; | ||
case '*': return left * right; | ||
case '/': return left / right; | ||
default: throw new Error(`unknown infix operator: '${operator}'`); | ||
let isEqual = (a, b) => { | ||
if (isArray(a) && isArray(b) && a.length === b.length) { | ||
for (let i = 0; i < a.length; i++) { | ||
if (!isEqual(a[i], b[i])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
infixRules['**'] = (left, token, ctx) => { | ||
let operator = token.kind; | ||
let right = ctx.parse('**-right-associative'); | ||
testMathOperands(operator, left, right); | ||
if (typeof left !== typeof right) { | ||
throw new InterpreterError(`TypeError: ${typeof left} ${operator} ${typeof right}`); | ||
} | ||
return Math.pow(left, right); | ||
if (isFunction(a)) { | ||
return a === b; | ||
} | ||
if (isObject(a) && isObject(b)) { | ||
let keys = Object.keys(a).sort(); | ||
if (!isEqual(keys, Object.keys(b).sort())) { | ||
return false; | ||
} | ||
for (let k of keys) { | ||
if (!isEqual(a[k], b[k])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
return a === b; | ||
}; | ||
infixRules['['] = (left, token, ctx) => parseInterval(left, token, ctx); | ||
infixRules['.'] = (left, token, ctx) => { | ||
if (isObject(left)) { | ||
let key = ctx.require('identifier').value; | ||
if (left.hasOwnProperty(key)) { | ||
return left[key]; | ||
let testMathOperands = (operator, left, right) => { | ||
if (operator === '+' && !(isNumber(left) && isNumber(right) || isString(left) && isString(right))) { | ||
throw expectationError('infix: +', 'numbers/strings + numbers/strings'); | ||
} | ||
throw new InterpreterError(`object has no property "${key}"`); | ||
} | ||
throw expectationError('infix: .', 'objects'); | ||
if (['-', '*', '/', '**'].some(v => v === operator) && !(isNumber(left) && isNumber(right))) { | ||
throw expectationError(`infix: ${operator}`, `number ${operator} number`); | ||
} | ||
return | ||
}; | ||
infixRules['('] = (left, token, ctx) => { | ||
if (isFunction(left)) { | ||
return left.apply(null, parseList(ctx, ',', ')')); | ||
} | ||
throw expectationError('infix: f(args)', 'f to be function'); | ||
}; | ||
infixRules['=='] = infixRules['!='] = infixRules['<='] = | ||
infixRules['>='] = infixRules['<'] = infixRules['>'] | ||
= (left, token, ctx) => { | ||
let operator = token.kind; | ||
let right = ctx.parse(operator); | ||
testComparisonOperands(operator, left, right); | ||
switch (operator) { | ||
case '>=': return left >= right; | ||
case '<=': return left <= right; | ||
case '>': return left > right; | ||
case '<': return left < right; | ||
case '==': return isEqual(left, right); | ||
case '!=': return !isEqual(left, right); | ||
default: throw new Error('no rule for comparison operator: ' + operator); | ||
let testComparisonOperands = (operator, left, right) => { | ||
if (operator === '==' || operator === '!=') { | ||
return null; | ||
} | ||
}; | ||
infixRules['||'] = infixRules['&&'] = (left, token, ctx) => { | ||
let operator = token.kind; | ||
let right = ctx.parse(operator); | ||
switch (operator) { | ||
case '||': return isTruthy(left) || isTruthy(right); | ||
case '&&': return isTruthy(left) && isTruthy(right); | ||
default: throw new Error('no rule for boolean operator: ' + operator); | ||
} | ||
}; | ||
let test = ['>=', '<=', '<', '>'].some(v => v === operator) | ||
&& (isNumber(left) && isNumber(right) || isString(left) && isString(right)); | ||
infixRules['in'] = (left, token, ctx) => { | ||
let right = ctx.parse(token.kind); | ||
if (isObject(right)) { | ||
if (!isString(left)) { | ||
throw expectationError('Infix: in-object', 'string on left side'); | ||
if (!test) { | ||
throw expectationError(`infix: ${operator}`, `numbers/strings ${operator} numbers/strings`); | ||
} | ||
right = Object.keys(right); | ||
} else if (isString(right)) { | ||
if (!isString(left)) { | ||
throw expectationError('Infix: in-string', 'string on left side'); | ||
} | ||
// short-circuit to indexOf since this is a substring operation | ||
return right.indexOf(left) !== -1; | ||
} else if (!isArray(right)) { | ||
throw expectationError('Infix: in', 'Array, string, or object on right side'); | ||
} | ||
return right.some(r => isEqual(left, r)); | ||
return | ||
}; | ||
module.exports = new PrattParser({ | ||
ignore: '\\s+', // ignore all whitespace including \n | ||
patterns: { | ||
number: '[0-9]+(?:\\.[0-9]+)?', | ||
identifier: '[a-zA-Z_][a-zA-Z_0-9]*', | ||
string: '\'[^\']*\'|"[^"]*"', | ||
// avoid matching these as prefixes of identifiers e.g., `insinutations` | ||
true: 'true(?![a-zA-Z_0-9])', | ||
false: 'false(?![a-zA-Z_0-9])', | ||
in: 'in(?![a-zA-Z_0-9])', | ||
null: 'null(?![a-zA-Z_0-9])', | ||
}, | ||
tokens: [ | ||
'**', ...'+-*/[].(){}:,'.split(''), | ||
'>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', | ||
'true', 'false', 'in', 'null', 'number', | ||
'identifier', 'string', | ||
], | ||
precedence: [ | ||
['||'], | ||
['&&'], | ||
['in'], | ||
['==', '!='], | ||
['>=', '<=', '<', '>'], | ||
['+', '-'], | ||
['*', '/'], | ||
['**-right-associative'], | ||
['**'], | ||
['[', '.'], | ||
['('], | ||
['unary'], | ||
], | ||
prefixRules, | ||
infixRules, | ||
}); | ||
exports | ||
.Interpreter = Interpreter; |
@@ -76,3 +76,3 @@ var {SyntaxError} = require('./error'); | ||
if (source.slice(offset) !== '') { | ||
throw new SyntaxError(`unexpected EOF for '${source}' at '${source.slice(offset)}'`, | ||
throw new SyntaxError(`Unexpected input for '${source}' at '${source.slice(offset)}'`, | ||
{start: offset, end: source.length}); | ||
@@ -79,0 +79,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
86292
14
1295
828