onec-syntaxparser
Version:
Parser for 1C bsl language based on regexp
559 lines (464 loc) • 24.5 kB
JavaScript
"use strict";
////////////////////////////////////////////////////////////////////////////////////////
////{ Cкрипт-библиотека SyntaxAnalysis (SyntaxAnalysis.js) для проекта "Снегопат"
////
//// Описание: Реализует функционал по cинтаксическому анализу исходного кода на
//// внутреннем языке 1С:Предприятия 8.
////
//// Основана на исходном коде скриптлета SyntaxAnalysis.wsc для проекта OpenConf.
////
//// Автор SyntaxAnalysis.wsc: Алексей Диркс <adirks@ngs.ru>
//// Автор порта: Александр Кунташов <kuntashov@gmail.com>
////}
////////////////////////////////////////////////////////////////////////////////////////
var Loki = require('lokijs');
function syntaxanalysis(){
return SyntaxAnalysis;
}
function SyntaxAnalysis() {
};
SyntaxAnalysis.prototype.parse = function (source) {
return new _1CModule(source);
};
SyntaxAnalysis.prototype.AnalyseTextDocument = function (source) {
return new _1CModule(source)
}
SyntaxAnalysis.prototype.Create1CModuleContextDescription = function(initValueTable) {
return new _1CModuleContextDescription(initValueTable);
}
SyntaxAnalysis.prototype.Create1CMethodDescription = function(parentModule) {
return new _1CMethodDescription(parentModule);
}
////////////////////////////////////////////////////////////////////////////////////////
////{ Регулярные выражения для поиска конструкций встроенного языка 1С.
////TODO:
//// - Удалить из регулярок определения метода Далее - не имеет смысла для 8.х
//// - Описать индексы и назначения группировок, подобно тому, как сделано для RE_VAR.
SyntaxAnalysis.prototype.RE_COMMENT = new RegExp('^\\s*((?:(?:(?:"[^"]")*)|(?:[^/]*)|(?:[^/]+/))*)(//.*)?\\s*$', "");
SyntaxAnalysis.prototype.RE_ISCOMMENT = new RegExp('^\\s*\\/\\/.*', '');
/* Группировки: 1: Объявление метода (процедура/функция), 2: Имя метода, 3: Список параметров метода строкой, 4: "Далее" - имеет смысл только для 7.7. */
//SyntaxAnalysis.RE_PROC = new RegExp('^\\s*((?:procedure)|(?:function)|(?:процедура)|(?:функция))\\s+([\\wА-яёЁ\\d]+)\\s*\\(([\\wА-яёЁ\\d\\s,.="\']*)\\)\\s*((?:forward)|(?:далее))?(.*)$', "i");
SyntaxAnalysis.prototype.RE_PROC = new RegExp('^\\s*((?:procedure)|(?:function)|(?:процедура)|(?:функция))\\s+([\\wА-яёЁ\\d]+)\\s*\\(', 'i');
SyntaxAnalysis.prototype.RE_PARAM = new RegExp('(?:(?:Val)|(?:Знач)\\s+)?([\\wА-яёЁ\\d]+)(\\s*=\\s*(?:(?:"[^"]")|(?:[^,)]*))*)?', "ig");
SyntaxAnalysis.prototype.RE_PARAM_END = new RegExp('([\\wА-яёЁ\\d\\s,.="\']*)\\)(.*)', 'i');
SyntaxAnalysis.prototype.RE_PROC_END = new RegExp('((?:EndProcedure)|(?:EndFunction)|(?:КонецПроцедуры)|(?:КонецФункции))', "i");
SyntaxAnalysis.prototype.RE_VARS_DEF = new RegExp('^\\s*(?:(?:Var)|(?:Перем))\\s*([\\wА-яёЁ\\d,=\\[\\]\\s]*)(\\s+экспорт\\s*)?([\\s;]*)(.*?)$', "i");
SyntaxAnalysis.prototype.RE_RPOC_EXPORT = new RegExp('\\)\\s*(Экспорт|Export)', "i");
/* Группировки: 1: Имя переменной, 2: Определение размерности массива, 3: Экспорт, 4: Конечный символ ("," или пусто - конец строки). */
SyntaxAnalysis.prototype.RE_VAR = new RegExp('([\\wА-яёЁ\\d]+)\\s*(\\[[\\d\\s,]*\\])?(\\s+экспорт\\s*)?(?:\\s*(?:,|;|$))', "ig");
SyntaxAnalysis.prototype.RE_VAR_ASSIGN = new RegExp('([\\wА-яёЁ\\d.]+)\\s*=\\s*(([^;]*);)?', "g");
SyntaxAnalysis.prototype.RE_CALL = new RegExp('([\\wА-яёЁ\\d.]+)\\s*\\(', "g");
SyntaxAnalysis.prototype.RE_SPACE = new RegExp('\\s+', "g");
//FIXME:RE_PROC_TORMOZIT пока не используеться, т.к. нет определения НаКлиенте, НаСервере и т.д. для тонкого клиента.
SyntaxAnalysis.prototype.RE_PROC_TORMOZIT = new RegExp('((Процедура|Функция)(?://[^\\n]*\\n|\\s|^|$)*([А-Яа-я_A-Za-z][А-Яа-я_A-Za-z\\d]*)(?://[^\\n]*\\n|\\s|^|$)*\\(([^\\)]*)\\)((?://[^\\n]*\\n|\\s|^|$)*Экспорт)?)((?:(?:"(?:(?:"")|[^"\\n$])*(?:(?://[^\\n]*\\n|\\s|^|$)*\\|(?:(?:"")|[^"\\n$])*)*(?:"|$)|\\.Конец(?:Процедуры|Функции)|\\r|\\n|.)*?))[^А-Яа-я_A-Za-z0-9\\."]Конец(?:Процедуры|Функции)[^А-Яа-я_A-Za-z0-9]|#[^\\n]*\\n|(?://[^\\n]*\\n|\\s|^|$)', 'igm')
SyntaxAnalysis.prototype.RE_PARAM_TORMOZIT = new RegExp('(?://[^\\n]*\\n|\\s|^|$)*(Знач\\s|Val\\s)?(?://[^\\n]*\\n|\\s|^|$)*([А-Яа-я_A-Za-z][А-Яа-я_A-Za-z0-9]+)(?://[^\\n]*\\n|\\s|^|$)*=?((?:(?://[^\\n]*\\n|\\s|^|$)*|("(?:(?:"")|[^"\\n$])*(?:(?://[^\\n]*\\n|\\s|^|$)*\\|(?:(?:"")|[^"\\n$])*)*(?:"|$))|(?:[^,\\n$]*))+)(?:,|$)','img');
SyntaxAnalysis.prototype.RE_CONTEXT = new RegExp('^\\s*&\\s*(AtClientAtServerNoContext|AtServerNoContext|AtClientAtServer|AtServer|AtClient|НаСервереБезКонтекста|НаКлиентеНаСервереБезКонтекста|НаКлиентеНаСервере|НаКлиенте|НаСервере)\\s*', 'i')
SyntaxAnalysis.prototype.CONTEXT = { "atclientatservernocontext" :"AtClientAtServerNoContext",
"atservernocontext" :"AtServerNoContext",
"atclientatserver" :"AtClientAtServer",
"atserver" :"AtServer",
"atclient" :"AtClient",
"насерверебезконтекста" :"НаСервереБезКонтекста",
"наклиентенасерверебезконтекста":"НаКлиентеНаСервереБезКонтекста",
"наклиентенасервере" :"НаКлиентеНаСервере",
"наклиенте" :"НаКлиенте",
"насервере" :"НаСервере"
}
SyntaxAnalysis.prototype.CALLSLIMIT = 100;
//SyntaxAnalysis
//SyntaxAnalysis.RE_CRLF = new RegExp('[\\n]+', "");
////} Регулярные выражения для поиска конструкций встроенного языка 1С.
SyntaxAnalysis.prototype.AnalyseParams = function(sourceCode, Meth) {
var Matches;
while( (Matches = this.RE_PARAM_TORMOZIT.exec(sourceCode)) != null ) {
Meth.Params.push({
"name":Matches[2],
"byval": Matches[1]!== undefined,
"default": Matches[3].toString().trim()
});
}
}
SyntaxAnalysis.prototype.AnalyseComments = function(sourceCode){
var result = sourceCode;
var Matches = this.RE_COMMENT.exec(sourceCode);
if( Matches != null )
result = Matches[1];
return result
}
SyntaxAnalysis.prototype.AnalyseModule = function (sourceCode, initValueTable, textWindow) {
var Meth;
var stStart = 0, stInProc = 1, stInModule = 2, stInVarsDef;
var state = stStart, PrevState;
var Match;
var Context = "";
var moduleContext = this.Create1CModuleContextDescription(initValueTable);
let ending = "\n";
if (sourceCode.indexOf("\r\n") > 0) {
ending = "\r\n";
}
var proc_count = 0;
var Lines = sourceCode.split(ending);
var n = Lines.length;
var i = 0;
var nextPart = '';
var description = new Array();
while (i < n)
{
var str = '';
if (nextPart)
{
str = nextPart;
}
else
{
str = this.AnalyseComments(Lines[i]);
}
switch( state )
{
case stStart:
case stInModule:
var Matches = this.RE_CONTEXT.exec(str);
if (Matches!=null) {
Context = this.CONTEXT[(""+Matches[1]).toLowerCase()];
} else {
Matches = this.RE_ISCOMMENT.exec(Lines[i]);
if (Matches!=null){
description.push(Lines[i]);
} else if (str.trim().length === 0) { //Если пустая строка тогда это уже не описание процедуры, а тупой комментарий модуля.
description = new Array();
}
}
Matches = this.RE_PROC.exec(str);
if( Matches != null )
{
Meth = this.Create1CMethodDescription(moduleContext);
Meth.description = description.join("\n");
description = new Array();
Meth.Name = Matches[2];
Meth.StartLine = i;
Meth.IsProc = (Matches[1].toLowerCase() == 'процедура' || Matches[1].toLowerCase() == 'procedure');
Meth.Context = (Context.length>0)?Context:"НаСервере"; //Пока только для тонкого клиента.
str = str.substr(Matches.lastIndex);
var strParams = '';
Matches = this.RE_PARAM_END.exec(str);
if (Matches!=null) {
strParams = Matches[1]
} else {
strParams = ''+str;
i++;
while (i<n) {
str = this.AnalyseComments(Lines[i]);
if ((Matches = this.RE_PARAM_END.exec(strParams+"\n"+str))!=null) {
strParams = Matches[1];
break;
} else {
strParams = strParams+"\n"+str;
}
i++;
}
}
this.AnalyseParams(strParams, Meth);
var endproc = Matches[2]
var MathesExport = this.RE_RPOC_EXPORT.exec(str);
if (MathesExport != null){
Meth.IsExport = true;
};
moduleContext.addMethod(Meth);
proc_count++;
state = stInProc;
if( (Match = this.RE_PROC_END.exec(endproc)) != null )
{
state = stInModule;
nextPart = endproc.substr(this.RE_PROC_END.lastIndex);
if (nextPart)
continue;
}
}
else if ((Matches = this.RE_VARS_DEF.exec(str)) != null)
{
str = Matches[1];
nextPart = Matches[4];
while( (Matches = this.RE_VAR.exec(str)) != null )
{
if( PrevState == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1], Matches[3], description);
}
if (nextPart)
continue;
str = str.replace(this.RE_SPACE, "");
if( str.substr(str.length-1) == ";" )
{
state = PrevState;
}
else if (str.substr(str.length-1) == ",")
{
PrevState = state;
state = stInVarsDef;
}
break;
} else {
while( (Matches = this.RE_CALL.exec(str)) != null )
{
if (Matches[1].charAt(0) == "."){
continue;
}
if (moduleContext.CallsPosition.length < this.CALLSLIMIT) {
moduleContext.CallsPosition.push({"call": Matches[1], "line": i, "character": Lines[i].indexOf(Matches[1])});
}
if(moduleContext._CallsUpper.indexOf(Matches[1].toUpperCase()) >= 0) {continue};
moduleContext.Calls.push(Matches[1]);
moduleContext._CallsUpper.push(Matches[1].toUpperCase());
if (moduleContext.CallsPosition.length > this.CALLSLIMIT){
moduleContext.CallsPosition.push({"call": Matches[1], "line": i, "character": Lines[i].indexOf(Matches[1])});
}
}
}
break;
case stInProc:
Matches = this.RE_PROC_END.exec(str);
if( Matches != null )
{
if( state == stInProc ) Meth.EndLine = i;
state = stInModule;
moduleContext.save(Meth);
}
else if( (Matches = this.RE_VARS_DEF.exec(str)) != null )
{
var exported = Matches[2];
var semicolon = Matches[3].replace(this.RE_SPACE, "");
str = Matches[1];
while( (Matches = this.RE_VAR.exec(str)) != null )
{
if( state == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1], Matches[3], description);
}
if( semicolon != ";" )
{
PrevState = state;
state = stInVarsDef;
}
}
else
{
while( (Matches = this.RE_VAR_ASSIGN.exec(str)) != null )
{
var varName = Matches[1];
if( varName.indexOf(".", 0) >= 0 ) continue;
if (state == stInProc)
Meth.addVar(varName, null, true); // automatic var
else
moduleContext.addVar(varName);
}
if( state == stInProc )
{
while( (Matches = this.RE_CALL.exec(str)) != null )
{
if (Matches[1].charAt(0) == "."){
continue;
}
if (Meth.CallsPosition.length < this.CALLSLIMIT) {
Meth.CallsPosition.push(
{"call": Matches[1], "line": i, "character": Lines[i].indexOf(Matches[1])}
);
}
if(Meth._CallsUpper.indexOf(Matches[1].toUpperCase()) >= 0) {continue};
Meth.Calls.push(Matches[1]);
Meth._CallsUpper.push(Matches[1].toUpperCase());
//see up - continue
if (Meth.CallsPosition.length > this.CALLSLIMIT) {
Meth.CallsPosition.push(
{"call": Matches[1], "line": i, "character": Lines[i].indexOf(Matches[1])}
);
}
}
}
}
break;
case stInVarsDef:
while( (Matches = this.RE_VAR.exec(str)) != null )
{
if( PrevState == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1], Matches[3], description);
}
str = str.replace(this.RE_SPACE, "");
if( str.substr(str.length-1) == ";" )
state = PrevState;
break;
}
i++;
nextPart = '';
}
return moduleContext;
}
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CModule
function _1CModule(sourceText) {
//this.textWindow = textWindow;
this.context = new SyntaxAnalysis().AnalyseModule(sourceText, true);
}
/* Возвращает исходный код метода по названию метода. */
_1CModule.prototype.getMethod = function(methodName) {
var method = this.context.getMethodByName(methodName);
if (!method) return undefined;
return method;
}
/* Возвращает таблицу значений с описаниями методов модуля. */
_1CModule.prototype.getMethodsTable = function() {
return this.context._vtAllMethods;
}
/* Возвращает описание метода по номеру строки, находящейся внутри метода. */
_1CModule.prototype.getMethodByLineNumber = function (lineNo) {
var methods = this.context.Methods;
for (var i=0; i<methods.length; i++)
{
/* Помним, что нумерация строк начинается с 1,
а строки модуля в SyntaxAnalysis проиндексированы с 0. */
if (methods[i].StartLine + 1 <= lineNo && lineNo <= methods[i].EndLine + 1)
return methods[i];
}
return undefined;
}
/* Возвращает описание метода, которому принадлежит текущая строка
(строка, в которой находится курсор). */
_1CModule.prototype.getActiveLineMethod = function () {
throw("Not implement");
/*let pos = this.textWindow.GetCaretPos();
return this.getMethodByLineNumber(pos.beginRow);*/
}
////} _1CModule
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CModuleContextDescription
function _1CModuleContextDescription(initValueTable) {
// Массив всех методов модуля.
this.Methods = new Array();
// Ассоциативный массив Имя метода -> _1CMethodDescription
this._methodsByName = {};
// Массив всех явным образом объявленных переменных модуля.
this.ModuleVars = {};
// Ассоциативный массив Имя переменной -> Тип переменной (пока тип всегда null).
this._moduleVarsTypes = {};
this.Calls = new Array();
this._CallsUpper = new Array();
this.CallsPosition = new Array();
this.db = new Loki('loki.json');
this._vtAllMethods = null;
if (initValueTable)
{
this._vtAllMethods = this.db.addCollection("ValueTable");
}
}
_1CModuleContextDescription.prototype.NewString = function () {
return {
name:"", //'Имя процедуры/функции'
isproc:false, //'Процедура = true, Функция = false
line:0, //начало
endline:0, //конец процедуры
context:"", //'Контекст компиляции модуля'
_method:"", // object
isexport: false,
description: ""
}
};
_1CModuleContextDescription.prototype.addMethod = function (method) {
if (this._methodsByName[method.Name])
console.log('Метод ' + method.name + 'уже был объявлен ранее в этом модуле!');
this.Methods.push(method);
this._methodsByName[method.Name] = method;
// Добавляем метод в таблицу значений.
}
_1CModuleContextDescription.prototype.save = function (method) {
if (this._vtAllMethods) {
if (this._vtAllMethods) {
var methRow = this.NewString();
methRow.name = method.Name;
methRow.isproc = method.IsProc;
methRow.line = method.StartLine;
methRow.endline = method.EndLine;
methRow.context = method.Context;
methRow.isexport = method.IsExport;
methRow.description = method.description;
methRow._method = method;
this._vtAllMethods.insert(methRow);
}
}
}
_1CModuleContextDescription.prototype.getMethodByName = function (name) {
return this._methodsByName[name];
}
_1CModuleContextDescription.prototype.addVar = function (name, isExport, description, type, auto) {
if (this._moduleVarsTypes[name] === undefined)
{
this._moduleVarsTypes[name] = (type === undefined) ? null : type;
this.ModuleVars[name] = {};
this.ModuleVars[name].isExport = (isExport) ? true : false;
this.ModuleVars[name].description = description.join(" ").replace(new RegExp("\/\/\s*"), "").replace(new RegExp(/\t/), " ");
}
}
_1CModuleContextDescription.prototype.getVarType = function (name) {
return this._moduleVarsTypes[name];
}
////} _1CModuleContextDescription
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CMethodDescription
function _1CMethodDescription(parentModule) {
// Идентификатор (имя) метода.
this.Name = "";
// Тип метода. Если истина - то это Процедура, иначе - это функция.
this.IsProc = false;
// Массив параметров метода.
this.Params = new Array();
// Массив явным образом объявленных переменных.
this.DeclaredVars = new Array();
// Массив автоматических локальных переменных (не объявленных явным образом).
this.AutomaticVars = new Array();
// Список вызовов: массив методов, вызываемых из данного метода.
this.Calls = new Array();
this._CallsUpper = new Array();
this.CallsPosition = new Array();
// Номер строки объявления метода.
this.StartLine = 0;
// Номер строки завершения объявления метода.
this.EndLine = 0;
// Ассоциативный массив Имя переменной -> Тип переменной (пока тип всегда null).
this._varsTypes = {};
// Контекст модуля, в котором объявлен данный метод (_1CModuleContextDescription).
this.parentModule = parentModule;
// Экспортная процедура или нет.
this.IsExport = false;
this.description = "";
}
_1CMethodDescription.prototype.addVar = function (name, type, auto) {
if (this._varsTypes[name] === undefined)
{
if (this.Params.indexOf(name) >= 0)
return;
if (this.parentModule && this.parentModule.getVarType(name) !== undefined)
return;
this._varsTypes[name] = (type === undefined) ? null : type;
if (auto)
this.AutomaticVars.push(name);
else
this.DeclaredVars.push(name);
}
}
_1CMethodDescription.prototype.getVarType = function (name) {
return this._varsTypes[name];
}
////} _1CMethodDescription
////////////////////////////////////////////////////////////////////////////////////////
////{ Вспомогательные функции объекта Array
if(!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
for(var i = fromIndex||0, length = this.length; i<length; i++)
if(this[i] === searchElement) return i;
return -1
};
};
////} Вспомогательные функции объекта Array
module.exports = SyntaxAnalysis;