From 59cca19e2a19f23698ab3bc07f4df24845842121 Mon Sep 17 00:00:00 2001 From: Mr-Rm Date: Mon, 2 Feb 2026 21:51:14 +0400 Subject: [PATCH] =?UTF-8?q?fix=20#1652:=20=D0=B0=D0=BD=D0=BD=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?,=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=BD=D0=B5=D0=B4=D0=BE=D0=BF=D1=83=D1=81=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BC=D0=B5=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SyntaxAnalysis/DefaultBslParser.cs | 257 ++++++++++-------- .../SyntaxAnalysis/LocalizedErrors.cs | 3 + .../OneScript.Language.Tests/ParserTests.cs | 54 +++- tests/annotations.os | 27 ++ 4 files changed, 215 insertions(+), 126 deletions(-) diff --git a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs index 7179ea767..ad0839376 100644 --- a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs +++ b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs @@ -21,8 +21,8 @@ public class DefaultBslParser private readonly ILexer _lexer; private readonly PreprocessorHandlers _preprocessorHandlers; - private Lexem _lastExtractedLexem; - + private Lexem _lastExtractedLexem; + private bool _inMethodScope; private bool _isMethodsDefined; private bool _isStatementsDefined; @@ -32,8 +32,8 @@ public class DefaultBslParser private readonly Stack _tokenStack = new Stack(); private bool _isInLoopScope; - private bool _enableException; - + private bool _enableException; + private readonly List _annotations = new List(); public DefaultBslParser( @@ -47,30 +47,30 @@ public DefaultBslParser( _nodeContext = new ParserContext(); } - private IErrorSink ErrorSink { get; } - - public IEnumerable Errors => ErrorSink.Errors ?? Array.Empty(); - + private IErrorSink ErrorSink { get; } + + public IEnumerable Errors => ErrorSink.Errors ?? Array.Empty(); + public BslSyntaxNode ParseStatefulModule() { - ModuleNode node; - + ModuleNode node; + _preprocessorHandlers.OnModuleEnter(); NextLexem(); node = new ModuleNode(_lexer.Iterator.Source, _lastExtractedLexem); PushContext(node); try - { - ParseModuleSections(); + { + ParseModuleSections(); } finally { PopContext(); } - _preprocessorHandlers.OnModuleLeave(); - + _preprocessorHandlers.OnModuleLeave(); + return node; } @@ -105,8 +105,8 @@ public BslSyntaxNode ParseExpression() return module; } - private void PushContext(NonTerminalNode node) => _nodeContext.PushContext(node); - + private void PushContext(NonTerminalNode node) => _nodeContext.PushContext(node); + private NonTerminalNode PopContext() => _nodeContext.PopContext(); private NonTerminalNode CurrentParent => _nodeContext.CurrentParent; @@ -114,13 +114,13 @@ public BslSyntaxNode ParseExpression() private void ParseModuleAnnotation() { if (_lastExtractedLexem.Type != LexemType.PreprocessorDirective) - return; - + return; + var annotationParser = _preprocessorHandlers .Slice(x => x is ModuleAnnotationDirectiveHandler) .Cast() - .ToList(); - + .ToList(); + if (annotationParser.Count == 0) return; @@ -131,7 +131,7 @@ private void ParseModuleAnnotation() foreach (var handler in annotationParser) { handled = handler.ParseAnnotation(ref _lastExtractedLexem, _lexer, _nodeContext); - if(handled) + if (handled) break; } @@ -139,8 +139,8 @@ private void ParseModuleAnnotation() { AddError(LocalizedErrors.DirectiveNotSupported(directive)); } - } - + } + foreach (var handler in annotationParser) { handler.OnModuleLeave(); @@ -150,19 +150,18 @@ private void ParseModuleAnnotation() private void ParseModuleSections() { ParseModuleAnnotation(); - BuildVariableSection(); - BuildMethodsSection(); + BuildVariablesSection(); + BuildMethodsSection(); + if (_annotations.Count != 0) + { + AddError(LocalizedErrors.AnnotationNotAllowed()); + } BuildModuleBody(); - - if (_annotations.Count != 0) - { - AddError(LocalizedErrors.UnexpectedEof()); - } - } - + } + #region Variables - - private void BuildVariableSection() + + private void BuildVariablesSection() { if (_lastExtractedLexem.Token != Token.VarDef && _lastExtractedLexem.Type != LexemType.Annotation) { @@ -177,8 +176,9 @@ private void BuildVariableSection() { while (true) { - BuildAnnotations(); - if (_lastExtractedLexem.Token != Token.VarDef) + BuildAnnotations(); + + if (_lastExtractedLexem.Token != Token.VarDef) break; if (!hasVars) @@ -187,79 +187,88 @@ private void BuildVariableSection() parent.AddChild(allVarsSection); } - BuildVariableDefinition(); + BuildVariablesDefinition(); } } finally { PopContext(); - } - + } + } - private void BuildVariableDefinition() - { + private void BuildVariablesDefinition() + { + if (_inMethodScope) + { + if (_isStatementsDefined) + { + AddError(LocalizedErrors.LateVarDefinition()); + return; + } + } + else if (_isMethodsDefined) + { + AddError(LocalizedErrors.LateVarDefinition()); + return; + } + while (true) { - var variable = _nodeContext.AddChild(new VariableDefinitionNode(_lastExtractedLexem)); - - ApplyAnnotations(variable); - - NextLexem(); - + NextLexem(); // skip opening VarDef or Comma + if (!IsUserSymbol(_lastExtractedLexem)) { - AddError(LocalizedErrors.IdentifierExpected()); + if(_lastExtractedLexem.Type == LexemType.Annotation) + AddError(LocalizedErrors.AnnotationNotAllowed()); + else + AddError(LocalizedErrors.IdentifierExpected()); return; } - if (_inMethodScope) + BuildVariable(); + + if (_lastExtractedLexem.Token == Token.Semicolon) { - if (_isStatementsDefined) - { - AddError(LocalizedErrors.LateVarDefinition()); - return; - } + break; } - else if (_isMethodsDefined) + + if (_lastExtractedLexem.Token != Token.Comma) { - AddError(LocalizedErrors.LateVarDefinition()); + AddError(LocalizedErrors.SemicolonExpected()); return; } + } - var symbolicName = _lastExtractedLexem.Content; - CreateChild(variable, NodeKind.Identifier, _lastExtractedLexem); + NextLexem(); // skip Semicolon + _annotations.Clear(); + } - NextLexem(); - if (_lastExtractedLexem.Token == Token.Export) + private void BuildVariable() + { + var variable = _nodeContext.AddChild(new VariableDefinitionNode(_lastExtractedLexem)); + if (!_inMethodScope) + foreach (var astNode in _annotations) { - if (_inMethodScope) - { - AddError(LocalizedErrors.ExportedLocalVar(symbolicName)); - return; - } - CreateChild(variable, NodeKind.ExportFlag, _lastExtractedLexem); - NextLexem(); - } + variable.AddChild(astNode); + } - if (_lastExtractedLexem.Token == Token.Comma) - { - continue; - } + var symbolicName = _lastExtractedLexem.Content; + CreateChild(variable, NodeKind.Identifier, _lastExtractedLexem); - if (_lastExtractedLexem.Token == Token.Semicolon) - { - NextLexem(); - } - else + NextLexem(); + if (_lastExtractedLexem.Token == Token.Export) + { + if (_inMethodScope) { - AddError(LocalizedErrors.SemicolonExpected()); + AddError(LocalizedErrors.ExportedLocalVar(symbolicName)); + return; } + CreateChild(variable, NodeKind.ExportFlag, _lastExtractedLexem); + NextLexem(); + } + } - break; - } - } - private void ApplyAnnotations(AnnotatableNode annotatable) { foreach (var astNode in _annotations) @@ -269,13 +278,13 @@ private void ApplyAnnotations(AnnotatableNode annotatable) _annotations.Clear(); } - #endregion + #endregion #region Methods private void BuildMethodsSection() { - if (_lastExtractedLexem.Type != LexemType.Annotation + if (_lastExtractedLexem.Type != LexemType.Annotation && !IsStartOfMethod(_lastExtractedLexem)) { return; @@ -313,14 +322,14 @@ private void BuildMethodsSection() private static bool IsStartOfMethod(in Lexem lex) { return lex.Token == Token.Async || lex.Token == Token.Procedure || lex.Token == Token.Function; - } - + } + private void BuildMethod() { Debug.Assert(IsStartOfMethod(_lastExtractedLexem)); - var method = _nodeContext.AddChild(new MethodNode()); - + var method = _nodeContext.AddChild(new MethodNode()); + ApplyAnnotations(method); PushContext(method); if (_lastExtractedLexem.Token == Token.Async) @@ -328,8 +337,8 @@ private void BuildMethod() method.IsAsync = true; _isInAsyncMethod = true; NextLexem(); - } - + } + try { BuildMethodSignature(); @@ -354,7 +363,7 @@ private void BuildMethodVariablesSection() { // для корректной перемотки вперед в случае ошибок в секции переменных PushStructureToken(_isInFunctionScope ? Token.EndFunction : Token.EndProcedure); - BuildVariableSection(); + BuildVariablesSection(); } finally { @@ -383,7 +392,7 @@ private void BuildMethodSignature() { var signature = _nodeContext.AddChild(new MethodSignatureNode(_lastExtractedLexem)); var isFunction = _lastExtractedLexem.Token == Token.Function; - CreateChild(signature, isFunction? NodeKind.Function : NodeKind.Procedure, _lastExtractedLexem); + CreateChild(signature, isFunction ? NodeKind.Function : NodeKind.Procedure, _lastExtractedLexem); _isInFunctionScope = isFunction; NextLexem(); if (!IsUserSymbol(_lastExtractedLexem)) @@ -410,30 +419,30 @@ private void BuildMethodParameters(MethodSignatureNode signature) } var paramList = new NonTerminalNode(NodeKind.MethodParameters, _lastExtractedLexem); - signature.AddChild(paramList); - + signature.AddChild(paramList); + NextLexem(); // ( if (_lastExtractedLexem.Token != Token.ClosePar) - while (true) - { - BuildMethodParameter(paramList); - - if (_lastExtractedLexem.Token == Token.ClosePar) + while (true) { - break; - } + BuildMethodParameter(paramList); - if (_lastExtractedLexem.Token == Token.Comma) - { - NextLexem(); - } - else - { - AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); - return; + if (_lastExtractedLexem.Token == Token.ClosePar) + { + break; + } + + if (_lastExtractedLexem.Token == Token.Comma) + { + NextLexem(); + } + else + { + AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); + return; + } } - } NextLexem(); // ) } @@ -501,15 +510,15 @@ private bool BuildDefaultParameterValue(NonTerminalNode param, NodeKind nodeKind } return true; - } - + } + #endregion - + private void BuildModuleBody() { if (!_lexer.Iterator.MoveToContent()) - return; - + return; + var moduleBody = new NonTerminalNode(NodeKind.ModuleBody, _lastExtractedLexem); var node = moduleBody.AddNode(new CodeBatchNode(_lastExtractedLexem)); PushContext(node); @@ -522,13 +531,19 @@ private void BuildModuleBody() PopContext(); } CurrentParent.AddChild(moduleBody); - } - + } + #region Annotations private void BuildAnnotations() { while (_lastExtractedLexem.Type == LexemType.Annotation) - { + { + if (_inMethodScope) + { + AddError(LocalizedErrors.AnnotationNotAllowed()); + return; + } + var node = BuildAnnotationDefinition(); _annotations.Add(node); } @@ -541,7 +556,6 @@ private AnnotationNode BuildAnnotationDefinition() { return node; } - private void BuildAnnotationParameters(AnnotationNode annotation) { if (_lastExtractedLexem.Token != Token.OpenPar) @@ -636,7 +650,10 @@ private void BuildCodeBatch(params Token[] endTokens) if (_lastExtractedLexem.Type != LexemType.Identifier && _lastExtractedLexem.Token != Token.EndOfText) { - AddError(LocalizedErrors.UnexpectedOperation()); + if (_lastExtractedLexem.Type == LexemType.Annotation) + AddError(LocalizedErrors.AnnotationNotAllowed()); + else + AddError(LocalizedErrors.UnexpectedOperation()); continue; } diff --git a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs index 809d3e695..a6e23d267 100644 --- a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs +++ b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs @@ -111,6 +111,9 @@ public static CodeError WrongHandlerName() => public static CodeError UnexpectedSymbol(char c) => Create($"Неизвестный символ {c}", $"Unexpected character {c}"); + public static CodeError AnnotationNotAllowed() => + Create("Аннотация неприменима в данном месте", "Annotation is not allowed here"); + public static CodeError DirectiveNotSupported(string directive) => Create($"Директива {directive} не разрешена в данном месте", $"Directive {directive} is not supported here"); diff --git a/src/Tests/OneScript.Language.Tests/ParserTests.cs b/src/Tests/OneScript.Language.Tests/ParserTests.cs index 7d965101e..92d498423 100644 --- a/src/Tests/OneScript.Language.Tests/ParserTests.cs +++ b/src/Tests/OneScript.Language.Tests/ParserTests.cs @@ -168,6 +168,53 @@ Процедура Процедура1() Экспорт .NextChildIs(NodeKind.Annotation); } + [Fact] + public void Check_AnnotationNotAllowed_InList() + { + var code = @" + &Аннотация + Перем Пер1, &Анн Пер2;"; + + CatchParsingError(code, err => err.First().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_InMethod() + { + var code = @" + Процедура Процедура1() + &Аннотация + Возврат + КонецПроцедуры"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_BeforeMethodsEnd() + { + var code = @" + Процедура Процедура1() + Ч = 0; + &Аннотация + КонецПроцедуры"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_InModuleBody() + { + var code = @" + Процедура Процедура1() + КонецПроцедуры + &Аннотация + Ч = 0"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] public void Check_Method_Parameters() { @@ -1283,12 +1330,7 @@ public void TestLocalExportVar() Перем Переменная Экспорт; КонецПроцедуры"; - CatchParsingError(code, err => - { - var errors = err.ToArray(); - errors.Should().HaveCount(1); - errors[0].Description.Should().Contain("Локальная переменная не может быть экспортирована"); - }); + CatchParsingError(code, err => err.First().ErrorId.Should().Be("ExportedLocalVar")); } [Fact] diff --git a/tests/annotations.os b/tests/annotations.os index a095cf5c5..b18a75c20 100644 --- a/tests/annotations.os +++ b/tests/annotations.os @@ -15,6 +15,7 @@ ВсеТесты.Добавить("ТестДолжен_ПроверитьАннотацииПолейЗагрузитьСценарийИзСтроки"); ВсеТесты.Добавить("ТестДолжен_ПроверитьАннотациюКакЗначениеПараметраАннотации"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьПолучениеАннотацийСпискаПеременных"); Возврат ВсеТесты; @@ -225,3 +226,29 @@ КонецЕсли; КонецПроцедуры + +Процедура ТестДолжен_ПроверитьПолучениеАннотацийСпискаПеременных() Экспорт + + ТекстСценария = "&Аннотация1 + |Перем Поле1 Экспорт, Поле2; + |Перем Поле3; + |"; + + Сценарий = ЗагрузитьСценарийИзСтроки(ТекстСценария); + + Рефлектор = Новый Рефлектор; + ТаблицаСвойств = Рефлектор.ПолучитьТаблицуСвойств(Сценарий, Истина); + + юТест.ПроверитьРавенство(ТаблицаСвойств.Количество(), 3, "Ожидали, что в таблице свойств будет 3 свойства"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[0].Аннотации.Количество(), 1, "Ожидали, что Поле1 имеет 1 аннотацию"); + ИмяАннотации = ТаблицаСвойств[0].Аннотации[0].Имя; + юТест.ПроверитьРавенство(ИмяАннотации, "Аннотация1", "Ожидали, что Поле1 имеет аннотацию Аннотация1"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[1].Аннотации.Количество(), 1, "Ожидали, что Поле2 имеет 1 аннотацию"); + ИмяАннотации = ТаблицаСвойств[1].Аннотации[0].Имя; + юТест.ПроверитьРавенство(ИмяАннотации, "Аннотация1", "Ожидали, что Поле2 имеет аннотацию Аннотация1"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[2].Аннотации.Количество(), 0, "Ожидали, что Поле3 не имеет аннотаций"); + +КонецПроцедуры