Files
enviPy-bayer/static/js/ketcher2/node_modules/varstream/src/VarStreamReader.js
2025-06-23 20:13:54 +02:00

439 lines
14 KiB
JavaScript
Executable File

/*
* Copyright (C) 2012-2013 Nicolas Froidure
*
* This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 3. It is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
*/
// AMD + global + NodeJS : You can use this object by inserting a script
// or using an AMD loader (like RequireJS) or using NodeJS
(function(root,define){ define([], function() {
'use strict';
// START: Module logic start
// Constructor
function VarStreamReader (scope, prop, options) {
// Keep a ref to the root scope
this.rootScope={root: scope, prop: prop};
// Save the options
this.options=options;
// Store current scopes for backward references
this.previousNodes=[];
// The parse state
this.state=PARSE_NEWLINE;
// The current values
this.leftValue='';
this.rightValue='';
this.operator='';
this.escaped=ESC_NONE;
}
// Static consts
VarStreamReader.STRICT_MODE=1;
VarStreamReader.OPTIONS=VarStreamReader.STRICT_MODE;
// Constants
// Chars
var CHR_ENDL = '\n'
, CHR_CR = '\r'
, CHR_SEP = '.'
, CHR_BCK = '^'
, CHR_EQ = '='
, CHR_ESC = '\\'
, CHR_PLU = '+'
, CHR_MIN = '-'
, CHR_MUL = '*'
, CHR_DIV = '/'
, CHR_MOD = '%'
, CHR_REF = '&'
, CHR_NEW = '!'
, CHR_COM = '#'
// Chars sets
, EQ_OPS = [CHR_PLU,CHR_MIN,CHR_MUL,CHR_DIV,CHR_MOD,CHR_REF]
, ARRAY_OPS = [CHR_PLU,CHR_MUL,CHR_NEW]
, ARRAY_NODE_CHARS = /^[0-9]+$/
, PROP_NODE_CHARS = /^[a-zA-Z0-9_]+$/
, BCK_CHARS = /^\^[0-9]*$/
// Parsing status
, PARSE_NEWLINE = 1
, PARSE_LVAL = 2
, PARSE_OPERATOR = 3
, PARSE_RVAL = 4
, PARSE_MLSTRING = 5
, PARSE_COMMENT = 6
, PARSE_SILENT = 7
// Escape status
, ESC_NONE = 0
, ESC_LF = 1
, ESC_ALL = 3
;
VarStreamReader.prototype.resolveScope = function (val) {
var nodes = val.split(CHR_SEP)
, scope = this.rootScope
, n = 0
;
// Looking for backward refs in the first node
if(nodes[0] && nodes[0][0] == CHR_BCK) {
// if no numbers adding every previous nodes
if(nodes[0] == CHR_BCK) {
n = this.previousNodes.length ? this.previousNodes.length - 1 : 0;
// if numbers
} else {
// check it
if(!BCK_CHARS.test(nodes[0])) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new Error('Malformed backward reference.');
}
return null;
}
n = parseInt(nodes[0].substring(1), 10);
}
if(n > this.previousNodes.length) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new SyntaxError('Backward reference index is greater than the'
+ ' previous node max index.');
}
return null;
}
this.previousNodes.length = n;
nodes.shift();
nodes.unshift.apply(nodes,this.previousNodes);
}
// Looping throught each nodes
for(var i=0, j=nodes.length; i<j; i++) {
// Checking if the node is not empty
if(''===nodes[i]) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new Error('The leftValue can\'t have empty nodes ('+val+').');
}
return null;
}
// Array operators
if(-1!==ARRAY_OPS.indexOf(nodes[i])||ARRAY_NODE_CHARS.test(nodes[i])) {
// Ensure the scope is an array
if('undefined'=== typeof scope.root[scope.prop]
||!(scope.root[scope.prop] instanceof Array)) {
scope.root[scope.prop]=[];
}
if(nodes[i]===CHR_PLU) {
nodes[i]=scope.root[scope.prop].length;
}
if(nodes[i]===CHR_MUL) {
nodes[i]=scope.root[scope.prop].length-1;
}
if(nodes[i]===CHR_NEW) {
nodes[i]=scope.root[scope.prop].length=0;
}
} else {
// Checking node chars
if(!PROP_NODE_CHARS.test(nodes[i])) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new SyntaxError('Illegal chars found in a the node'
+ ' "'+nodes[i]+'".');
}
return null;
}
// Ensure the scope is an object
if('undefined'=== typeof scope.root[scope.prop]
||!(scope.root[scope.prop] instanceof Object)) {
scope.root[scope.prop]={};
}
}
// Resolving the node scope
scope={
root : scope.root[scope.prop],
prop : nodes[i]
};
}
// Keep previous nodes for backwards references
this.previousNodes = nodes;
return scope;
};
VarStreamReader.prototype.read = function (chunk) {
// Looping throught chunk chars
for(var i=0, j=chunk.length; i<j; i++) {
// detect escaped chars
if(chunk[i]===CHR_ESC && (
this.state===PARSE_RVAL
||this.state===PARSE_SILENT
||this.state===PARSE_MLSTRING
)
) {
if(this.escaped) {
this.escaped=ESC_NONE;
} else {
this.escaped=ESC_ALL;
continue;
}
}
// parsing chars according to the current state
switch(this.state) {
// Continue while newlines
case PARSE_NEWLINE:
this.escaped=ESC_NONE;
this.operator='';
this.leftValue='';
this.rightValue='';
if(chunk[i]===CHR_ENDL||chunk[i]===CHR_CR) {
continue;
}
// Read left value content
case PARSE_LVAL:
// Detect comments
if(chunk[i]===CHR_COM) {
this.state=PARSE_COMMENT;
continue;
}
// Detect special operators
if(this.leftValue.lastIndexOf(CHR_SEP)!=this.leftValue.length-1
&&-1!==EQ_OPS.indexOf(chunk[i])) {
this.state=PARSE_OPERATOR;
this.operator=chunk[i];
continue;
}
// Detect the = operator
if(CHR_EQ===chunk[i]) {
this.state=PARSE_RVAL;
this.operator=chunk[i];
continue;
}
// Fail if a new line is found
if(chunk[i]===CHR_ENDL||chunk[i]===CHR_CR) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw SyntaxError('Unexpected new line found while parsing '
+' a leftValue.');
}
this.state=PARSE_NEWLINE;
continue;
}
// Store LVAL chars
this.state=PARSE_LVAL;
this.leftValue+=chunk[i];
continue;
// Read right value content
case PARSE_RVAL:
// Left value should not be empty
if(''===this.leftValue) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw SyntaxError('Found an empty leftValue.');
}
this.state=PARSE_SILENT;
}
// Stop if a new line is found
if(chunk[i]===CHR_ENDL||chunk[i]===CHR_CR) {
// rightValue can be empty only with the = operator
// or if the string is multiline
if(this.operator!=CHR_EQ&&''===this.rightValue
&&this.escaped===ESC_NONE) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw SyntaxError('Found an empty rightValue.');
}
this.state=PARSE_NEWLINE;
continue;
}
// Compute rval
// if it's a ref
if(this.operator===CHR_REF) {
this.rightValue=this.resolveScope(this.rightValue);
} else if('null'===this.rightValue) {
this.rightValue=null;
// Booleans
} else if('true'===this.rightValue) {
this.rightValue=true;
} else if('false'===this.rightValue) {
this.rightValue=false;
// Numbers
} else if('NaN'===this.rightValue) {
this.rightValue=NaN;
} else if(/^\-?([0-9]+(\.[0-9]+)?|Infinity)$/
.test(this.rightValue)) {
this.rightValue=Number(this.rightValue);
}
// Compute lval
this.leftValue=this.resolveScope(this.leftValue);
// set rval in lval (with operators)
if(null!==this.leftValue) {
switch(this.operator) {
case CHR_REF:
this.leftValue.root[this.leftValue.prop] = this.rightValue ?
this.rightValue.root[this.rightValue.prop] :
null;
break;
case CHR_EQ:
if(this.rightValue!=='' || 'string' ===
typeof this.leftValue.root[this.leftValue.prop]) {
this.leftValue.root[this.leftValue.prop]=this.rightValue;
} else {
delete this.leftValue.root[this.leftValue.prop];
}
break;
case CHR_PLU:
this.leftValue.root[this.leftValue.prop] +=
null === this.rightValue ? NaN : this.rightValue;
break;
case CHR_MIN:
this.leftValue.root[this.leftValue.prop] -=
null === this.rightValue ? NaN : this.rightValue;
break;
case CHR_MUL:
this.leftValue.root[this.leftValue.prop] *=
null === this.rightValue ? NaN : this.rightValue;
break;
case CHR_DIV:
this.leftValue.root[this.leftValue.prop] /=
null === this.rightValue ? NaN : this.rightValue;
break;
case CHR_MOD:
this.leftValue.root[this.leftValue.prop] %=
null === this.rightValue ? NaN : this.rightValue;
break;
}
}
// if the newline was escaped, continue to read the string
if(this.escaped) {
if(chunk[i]===CHR_CR) {
this.escaped=ESC_LF;
} else {
this.escaped=ESC_NONE;
}
if(null!==this.leftValue) {
this.state=PARSE_MLSTRING;
this.leftValue.root[this.leftValue.prop]+=chunk[i];
} else {
this.state=PARSE_SILENT;
}
} else {
this.state=PARSE_NEWLINE;
}
continue;
}
// Store RVAL chars
if(this.escaped) {
if(this.escaped==ESC_ALL) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw Error('Found an escape char but there was nothing to escape.');
}
this.rightValue+='\\';
}
this.escaped=ESC_NONE;
}
this.rightValue+=chunk[i];
continue;
// Parse the content of a multiline value
case PARSE_MLSTRING:
if(this.escaped) {
if(this.escaped===ESC_ALL&&chunk[i]===CHR_CR) {
this.escaped=ESC_LF;
} else if(chunk[i]===CHR_ENDL) {
this.escaped=ESC_NONE;
} else {
if(this.escaped===ESC_LF) {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new SyntaxError('Assuming a LF after an escaped CR, '
+chunk[i]+' found instead.');
}
} else {
if(this.options&VarStreamReader.STRICT_MODE) {
throw new SyntaxError('Found an escape char but there was'
+ ' nothing to escape.');
}
this.leftValue.root[this.leftValue.prop]+='\\';
}
this.escaped=ESC_NONE;
}
} else if(chunk[i]===CHR_ENDL||chunk[i]===CHR_CR) {
this.state=PARSE_NEWLINE;
continue;
}
// Store RVAL chars
this.leftValue.root[this.leftValue.prop]+=chunk[i];
continue;
// Finding the = char after an operator
case PARSE_OPERATOR:
if(chunk[i]===CHR_EQ) {
this.state=PARSE_RVAL;
continue;
}
if(this.options&VarStreamReader.STRICT_MODE) {
throw new SyntaxError('Unexpected char after the'
+ ' "'+this.operator+'" operator. Expected "="'
+ ' found "'+chunk[i]+'".');
}
if(chunk[i]===CHR_ENDL || chunk[i]===CHR_CR) {
this.state=PARSE_NEWLINE;
} else {
this.state=PARSE_SILENT;
}
continue;
// Parsing a comment content
case PARSE_COMMENT:
if((chunk[i]===CHR_ENDL&&!(this.escaped&ESC_LF))
||(chunk[i]===CHR_CR&&!(this.escaped&ESC_ALL))) {
this.state=PARSE_NEWLINE;
continue;
}
if(chunk[i]===CHR_CR&&(this.escaped&ESC_ALL)) {
this.escaped=ESC_LF;
}
if(chunk[i]===CHR_ENDL&&(this.escaped&ESC_LF)) {
this.escaped=ESC_NONE;
}
continue;
// Something was wrong, waiting for a newline to continue parsing
case PARSE_SILENT:
if((chunk[i]===CHR_ENDL&&!(this.escaped&ESC_LF))
||(chunk[i]===CHR_CR&&!(this.escaped&ESC_ALL))) {
this.state=PARSE_NEWLINE;
continue;
}
if(chunk[i]===CHR_CR&&(this.escaped&ESC_ALL)) {
this.escaped=ESC_LF;
}
if(chunk[i]===CHR_ENDL&&(this.escaped&ESC_LF)) {
this.escaped=ESC_NONE;
}
continue;
}
}
};
// END: Module logic end
return VarStreamReader;
});})(this,typeof define === 'function' && define.amd ?
// AMD
define :
// NodeJS
(typeof exports === 'object'?function (name, deps, factory) {
var root=this;
if(typeof name === 'object') {
factory=deps; deps=name;
}
module.exports=factory.apply(this, deps.map(function(dep){
return require(dep);
}));
}:
// Global
function (name, deps, factory) {
var root=this;
if(typeof name === 'object') {
factory=deps; deps=name;
}
this.VarStreamReader=factory.apply(this, deps.map(function(dep){
return root[dep.substring(dep.lastIndexOf('/')+1)];
}));
}.bind(this)
)
);