﻿$engine JScript
$uname StreamLib
$dname Работа с данными в формате потока (stream)
$addin global
$addin stdlib

////////////////////////////////////////////////////////////////////////////////////////
////{ Cкрипт-библиотека StreamLib (StreamLib.js) для проекта "Снегопат"
////
//// Описание: Реализует функционал по работе с данными во внутреннем 
//// формате 1С:Предприятия - "поток" (stream).
////
//// Автор: Александр Кунташов <kuntashov@gmail.com>, http://compaud.ru/blog
////}
////////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////////////
////{ StreamFactory
////

StreamFactory = {};

StreamFactory.CreateParser = function () {
    return new _StreamParser();
}

StreamFactory.CreateSerializer = function () {
    return new _StreamSerializer();
}

////} StreamFactory

////////////////////////////////////////////////////////////////////////////////////////
////{ StreamParser
////

function _StreamParser() {
    this._file = '';
    this.setStream('');
}

_StreamParser.prototype.setStream = function (stream) {
    this._stream = stream;
    this.pos = 0;
    this.len = this._stream.length;    
}

_StreamParser.prototype.getStream = function() {  
    return this._stream;
}

_StreamParser.prototype.readStreamFromFile = function(filepath) {    
    this._file = v8New("File", filepath);
    if (!this._file.Exist())
    {
        _logError("Файл не существует: " + this._file.FullName);
        return false;
    }
    
    var textDoc = v8New("TextDocument");
    textDoc.Read(this._file.FullName);
    
    this.setStream(textDoc.GetText());
    
    return true;
}

_StreamParser.prototype.parse = function() {
    try
    {
        return this._parse();
    }
    catch (e)
    {
        Message(e.description);
        return null;
    }
}

////////////////////////////////////////////////////////////////////////////////////////
//// Реализация парсера StreamParser.
////{

_StreamParser.prototype._parse = function() {
        
    if (!this.atEnd())
    {
        if (this.isEquals('{'))
            return this.readArray();
        
        this.errorSyntaxError('_parse:1'); 
    }
    
    this.errorUnexpectedEndOfStream('_parse:2');
}

_StreamParser.prototype.readArray = function() {
    
    var a = [];
    
    while (this.next())
    {
        if (this.isEquals('{'))
        {
            a.push(this.readArray())
        }
        else if (this.isEquals('}'))
        {
            return a;
        }
        else if (this.isEquals('"'))
        {
            a.push(this.readString());
        }
        else if (this.isNumber())
        {
            a.push(this.readNumber());
        }
        else if (this.isEquals(',') || this.isSpace()) 
        {
            // TODO: проверять ошибку: две подряд идущие запятые.
            //this.errorSyntaxError('readArray:2, coma');
        }
        else 
        {
            this.errorSyntaxError('readArray:1');
        }
    }
    
    this.errorUnexpectedEndOfStream('readArray:2');
}

_StreamParser.prototype.readNumber = function() {

    var num = this.charAt(this.pos);
    oneCrapca = true;
    isExp = false;
    
    while (this.next())
    {
        if (this.isNumber())
        {
            num += this.charAt(this.pos);
        }
        else if (this.isEquals(',') || this.isEquals('}'))
        {
            this.pos--;
            return 1 * num; // Преобразуем к числу.
        }
        else if (this.isEquals('.') && oneCrapca) {
            oneCrapca = false;
            num += this.charAt(this.pos);

        } else if ((this.isEquals('e') || this.isEquals('-') || this.isEquals('+')) && (oneCrapca == false)){
            debugger;
            //Message(this.charAt(this.pos));
            //num += this.charAt(this.pos);
            isExp = true
        } //else if((this.isEquals('-') || this.isEquals('+')) && isExp){
            //num += this.charAt(this.pos);
        //}
        else 
        {            
             this.errorSyntaxError('readNumber:1', 'цифра');
        }
    }
    this.errorUnexpectedEndOfStream('readNumber:2');
};
    
_StreamParser.prototype.readString = function() {
    
    var str = '';
    
    while (this.next())
    {        
        if (this.isEquals('"'))
        {
            if (!this.next())
                 this.errorUnexpectedEndOfStream('readString:1');
            
            /* Проверим следующий символ после кавычки. В синтаксически верном файле потока
            следующим символом может быть: еще одна ковычка - это означает экранированную кавычку,
            запятая или закрывающая фигурная скобка или несколько пробельных символов, а потом 
            запятая или закрывающая фигурная скобка - это значит строка закончилась. */
            if (this.isEquals('"'))
            {
                str += '"';
            }
            else 
            {
                while (this.isSpace() && this.next())
                    ; // Пропускаем пробельные символы.
                
                if (this.atEnd())
                    this.errorUnexpectedEndOfStream('readString:3');
                
                if (this.isEquals(',') || this.isEquals('}'))
                {
                    this.pos--;
                    return str;
                }
                
                this.errorSyntaxError('readString:4');                    
            }
        }
        else
        {
            str += this.charAt(this.pos);
        }
    }
    debugger;
    this.errorSyntaxError('readString:6');
    this.errorUnexpectedEndOfStream('readString:5');
}

_StreamParser.prototype.atEnd = function () {
    return (this.pos == this._stream.length);
};

_StreamParser.prototype.next = function () {                
    this.pos++;
    if (this.atEnd()){
        debugger;
    }

    return !this.atEnd();          
};  

_StreamParser.prototype.charAt = function (index) {
    return this._stream.charAt(index);
};
        
_StreamParser.prototype.isEquals = function (ch) {
    if (this.atEnd())
        return false;
        
    return (ch == this.charAt(this.pos))
};
        
_StreamParser.prototype.isSpace = function() {
    var ch = this.charAt(this.pos); 
    return (ch == ' ' || ch == "\t" || ch == "\r" || ch == "\n");
};
        
_StreamParser.prototype.isNumber = function() {
    var ch = this.charAt(this.pos);
    return ch == '0' || ch == '1' || ch == '2' || ch == '3' || ch == '4'
        || ch == '5' || ch == '6' || ch == '7' || ch == '8' || ch == '9';
}
       
//} Реализация парсера StreamParser.

////////////////////////////////////////////////////////////////////////////////////////
//// Обработка ошибок алгоритма парсинга в StreamParser.
////{

function _StreamParserSyntaxErrorException(description, errorPos, methodId) {
    this.methodId = methodId;
    this.description = description;
    this.errorPos = errorPos;
}

function _StreamParserUnexpectedEndOfStreamExeption(description, methodId) {
    this.methodId = methodId;
    this.description = description;
}

_StreamParser.prototype.errorSyntaxError = function (methodId) {
    var desc = this._methodIdRepr(methodId)
        + "Ошибка разбора потока: синтаксическая ошибка в позиции [" + this.pos + "]: "
        + this._getErrorContext();
        
    _logError(desc);
    
    throw new _StreamParserSyntaxErrorException(desc, this.pos, methodId);
},

_StreamParser.prototype.errorUnexpectedEndOfStream = function(methodId) {
    var desc = this._methodIdRepr(methodId) + "Ошибка разбора потока: Неожиданный конец потока!";
    
    _logError(desc);
    
    throw new _StreamParserUnexpectedEndOfStreamExeption(desc, methodId);
}

/* Возвращает контекст ошибки: строку, в которой произошла ошибки и строки выше и ниже
этой строки. Перед позицией, в которой обнаружена ошибка будет добавлен маркер '<!>', 
выделенный дополнительно пробелами слева и справа (по одному пробелу). */
_StreamParser.prototype._getErrorContext = function() {
    
    var linesBefore = 1;
    var linesAfter = 1;
    
    var context = ' <!> '; // Маркер позиции ошибки.
    
    // Символы левее ошибки, включая одну строку выше строки с ошибкой.
    for (var i=this.pos-1; linesBefore >= 0 && i >= 0; i--)
    {      
        var ch = this.charAt(i);
        context = ch + context;
        
        if (ch == "\n")
            linesBefore--;                
    }
    
    // Символы правее ошибки, включая одну строку ниже строки с ошибкой.
    for (var i=this.pos; linesAfter >= 0 && i<this.len; i++)
    {
        var ch = this.charAt(i);
        context = context + ch;
        
        if (ch == "\n")
            linesAfter--;                
    }
    
    return context;
}

_StreamParser.prototype._methodIdRepr = function(methodId) {
    return (methodId ? ("[" + methodId + "]: ") : "" );
}

function _logError(message) {
    Message('StreamParser: ' + message);
}

//} Обработка ошибок алгоритма парсинга в StreamParser.

////} StreamParser









