diff --git a/src/OneScript.Language/LanguageDef.cs b/src/OneScript.Language/LanguageDef.cs index 7f925859b..ecbbb9c76 100644 --- a/src/OneScript.Language/LanguageDef.cs +++ b/src/OneScript.Language/LanguageDef.cs @@ -107,6 +107,7 @@ static LanguageDef() AddToken(Token.RemoveHandler, "УдалитьОбработчик", "RemoveHandler"); AddToken(Token.Async, "Асинх", "Async"); AddToken(Token.Await, "Ждать", "Await"); + AddToken(Token.Goto, "Перейти", "Goto"); #endregion diff --git a/src/OneScript.Language/LexicalAnalysis/LabelLexerState.cs b/src/OneScript.Language/LexicalAnalysis/LabelLexerState.cs index 17dc88f49..d9734fb91 100644 --- a/src/OneScript.Language/LexicalAnalysis/LabelLexerState.cs +++ b/src/OneScript.Language/LexicalAnalysis/LabelLexerState.cs @@ -9,6 +9,11 @@ public class LabelLexerState : LexerState "Ожидается имя метки", "Label name expected" ); + + private static BilingualString INVALID_LABEL = new BilingualString( + "Неверно задана метка", + "Invalid label definition" + ); WordLexerState _wordExtractor = new WordLexerState(); @@ -25,11 +30,20 @@ public override Lexem ReadNextLexem(SourceCodeIterator iterator) throw CreateExceptionOnCurrentLine(MESSAGE_NAME_EXPECTED.ToString(), iterator); var result = _wordExtractor.ReadNextLexem(iterator); + if (!LanguageDef.IsUserSymbol(result)) + { + throw CreateExceptionOnCurrentLine(INVALID_LABEL.ToString(), iterator); + } + result.Type = LexemType.LabelRef; if (iterator.CurrentSymbol == SpecialChars.Colon) { result.Type = LexemType.Label; - iterator.MoveNext(); + var tail = iterator.ReadToLineEnd(); + if (tail.Trim().Length != 0) + { + throw CreateExceptionOnCurrentLine(INVALID_LABEL.ToString(), iterator); + } } result.Location = start; diff --git a/src/OneScript.Language/LexicalAnalysis/Token.cs b/src/OneScript.Language/LexicalAnalysis/Token.cs index 5b4d91f1d..aca7738f1 100644 --- a/src/OneScript.Language/LexicalAnalysis/Token.cs +++ b/src/OneScript.Language/LexicalAnalysis/Token.cs @@ -43,6 +43,7 @@ public enum Token RemoveHandler, Async, Await, + Goto, // operators Plus, diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/LabelNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/LabelNode.cs new file mode 100644 index 000000000..f26c8adc5 --- /dev/null +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/LabelNode.cs @@ -0,0 +1,14 @@ +using OneScript.Language.LexicalAnalysis; + +namespace OneScript.Language.SyntaxAnalysis.AstNodes +{ + public class LabelNode : LineMarkerNode + { + public LabelNode(Lexem labelLexem) : base(labelLexem.Location, NodeKind.Label) + { + LabelName = labelLexem.Content; + } + + public string LabelName { get; } + } +} \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs b/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs index 855ead38a..7c2b352da 100644 --- a/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs +++ b/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs @@ -51,9 +51,19 @@ private void CreateVisitors() _nodeVisitors[(int)NodeKind.RemoveHandler] = VisitHandlerOperation; _nodeVisitors[(int)NodeKind.NewObject] = (x) => VisitNewObjectCreation((NewObjectNode)x); _nodeVisitors[(int)NodeKind.Preprocessor] = (x) => VisitPreprocessorDirective((PreprocessorDirectiveNode)x); + _nodeVisitors[(int)NodeKind.Goto] = (x) => VisitGotoNode((NonTerminalNode)x); + _nodeVisitors[(int)NodeKind.Label] = (x) => VisitLabelNode((LabelNode)x); } + protected virtual void VisitGotoNode(NonTerminalNode node) + { + } + + protected virtual void VisitLabelNode(LabelNode node) + { + } + protected void SetDefaultVisitorFor(NodeKind kind, Action action) { _nodeVisitors[(int)kind] = action; diff --git a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs index 384f3ae31..9701bb69b 100644 --- a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs +++ b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs @@ -621,6 +621,12 @@ private void BuildCodeBatch(params Token[] endTokens) continue; } + if (_lastExtractedLexem.Type == LexemType.Label) + { + DefineLabel(_lastExtractedLexem); + continue; + } + if (_lastExtractedLexem.Type != LexemType.Identifier && _lastExtractedLexem.Token != Token.EndOfText) { AddError(LocalizedErrors.UnexpectedOperation()); @@ -642,6 +648,13 @@ private void BuildCodeBatch(params Token[] endTokens) PopStructureToken(); } + private void DefineLabel(Lexem label) + { + var node = new LabelNode(label); + _nodeContext.AddChild(node); + NextLexem(); + } + #region Statements private void BuildStatement() @@ -694,6 +707,9 @@ private void BuildComplexStructureStatement() case Token.Await: BuildGlobalCallAwaitOperator(); break; + case Token.Goto: + BuildGotoOperator(); + break; default: var expected = _tokenStack.Peek(); AddError(LocalizedErrors.TokenExpected(expected)); @@ -734,6 +750,22 @@ private BslSyntaxNode BuildExpressionAwaitOperator(Lexem lexem) return new ErrorTerminalNode(_lastExtractedLexem); } } + + private void BuildGotoOperator() + { + var gotoNode = new NonTerminalNode(NodeKind.Goto, _lastExtractedLexem); + NextLexem(); + + if (_lastExtractedLexem.Type != LexemType.LabelRef) + { + AddError(LocalizedErrors.LabelNameExpected()); + } + + gotoNode.AddChild(new LabelNode(_lastExtractedLexem)); + NextLexem(); + + _nodeContext.AddChild(gotoNode); + } private void CheckAsyncMethod() { diff --git a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs index b069f1656..e0b96014f 100644 --- a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs +++ b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs @@ -43,6 +43,9 @@ private static CodeError Create(string ru, string en, [CallerMemberName] string public static CodeError IdentifierExpected() => Create("Ожидается идентификатор", "Identifier expecting"); + + public static CodeError LabelNameExpected() + => Create("Ожидается имя метки", "Label name expected"); public static CodeError ExpressionSyntax() => Create("Ошибка в выражении", "Expression syntax error"); @@ -139,5 +142,8 @@ public static CodeError DuplicateMethodDefinition(string methodName) => public static CodeError SymbolNotFound(string symbol) => Create($"Неизвестный символ: {symbol}", $"Symbol not found {symbol}"); + + public static CodeError AsyncMethodsNotSupported() => + Create("Асинхронные методы не поддерживаются", "Async methods aren't supported"); } } \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/NodeKind.cs b/src/OneScript.Language/SyntaxAnalysis/NodeKind.cs index f8b8b0cb5..975941278 100644 --- a/src/OneScript.Language/SyntaxAnalysis/NodeKind.cs +++ b/src/OneScript.Language/SyntaxAnalysis/NodeKind.cs @@ -62,5 +62,7 @@ public enum NodeKind Preprocessor, Import, TopLevelExpression, + Label, + Goto } } \ No newline at end of file diff --git a/src/OneScript.Native/Compiler/ModuleCompiler.cs b/src/OneScript.Native/Compiler/ModuleCompiler.cs index 07c200a31..61acb2044 100644 --- a/src/OneScript.Native/Compiler/ModuleCompiler.cs +++ b/src/OneScript.Native/Compiler/ModuleCompiler.cs @@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ +using System; using System.Linq; using OneScript.Compilation; using OneScript.Compilation.Binding; @@ -166,8 +167,22 @@ protected override void VisitModuleVariable(VariableDefinitionNode varNode) } } + protected override void VisitGotoNode(NonTerminalNode node) + { + throw new NotSupportedException(); + } + + protected override void VisitLabelNode(LabelNode node) + { + throw new NotSupportedException(); + } + protected override void VisitMethod(MethodNode methodNode) { + if (methodNode.IsAsync) + { + AddError(LocalizedErrors.AsyncMethodsNotSupported(), methodNode.Location); + } var methodSymbol = Symbols.GetScope(Symbols.ScopeCount - 1).Methods[methodNode.Signature.MethodName]; var methodInfo = (BslNativeMethodInfo)methodSymbol.Method; diff --git a/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs b/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs index c37e0938b..e361f4e9f 100644 --- a/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs +++ b/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs @@ -224,9 +224,23 @@ private static string[] GetVariableNames(SymbolScope localCtx) return localCtx.Variables.Select(v => v.Name).ToArray(); } + + protected override void VisitGotoNode(NonTerminalNode node) + { + throw new NotSupportedException(); + } + + protected override void VisitLabelNode(LabelNode node) + { + throw new NotSupportedException(); + } protected override void VisitMethod(MethodNode methodNode) { + if (methodNode.IsAsync) + { + AddError(LocalizedErrors.AsyncMethodsNotSupported(), methodNode.Location); + } var signature = methodNode.Signature; var methodBuilder = NewMethod(); diff --git a/src/Tests/OneScript.Language.Tests/LexerTests.cs b/src/Tests/OneScript.Language.Tests/LexerTests.cs index 37552c9c8..2f13ab36c 100644 --- a/src/Tests/OneScript.Language.Tests/LexerTests.cs +++ b/src/Tests/OneScript.Language.Tests/LexerTests.cs @@ -634,9 +634,10 @@ public void Lexer_Ignores_Comments() } [Fact] - public void Leer_Extracts_Labels() + public void Lexer_Extracts_Labels() { - string code = "~ImALabel: ~LabelRef;"; + string code = "~ImALabel:\n" + + " ~LabelRef;"; var lexer = GetLexerForCode(code); var lex = lexer.NextLexem(); diff --git a/src/Tests/OneScript.Language.Tests/ParserTests.cs b/src/Tests/OneScript.Language.Tests/ParserTests.cs index f3495dc1a..41ed6f52a 100644 --- a/src/Tests/OneScript.Language.Tests/ParserTests.cs +++ b/src/Tests/OneScript.Language.Tests/ParserTests.cs @@ -1336,6 +1336,38 @@ public void DoubleAwaitIsForbidden() CatchParsingError(code, err => err.Single().ErrorId.Should().Be("ExpressionSyntax")); } + + [Fact] + public void Labels_Can_Appear_In_CodeBlocks() + { + var code = + @"А = 1; + ~Метка: + Б = 2; + "; + + var validator = ParseBatchAndGetValidator(code); + + validator.NextChildIs(NodeKind.Assignment); + validator.NextChildIs(NodeKind.Label); + validator.NextChildIs(NodeKind.Assignment); + } + + [Fact] + public void Goto_Can_Appear_In_CodeBlocks() + { + var code = + @"А = 1; + Перейти ~Метка; + Б = 2; + "; + + var validator = ParseBatchAndGetValidator(code); + + validator.NextChildIs(NodeKind.Assignment); + validator.NextChildIs(NodeKind.Goto); + validator.NextChildIs(NodeKind.Assignment); + } private static void CatchParsingError(string code) {