성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Roslyn 맛보기 - SyntaxTree 조작</h1> <p> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Roslyn 맛보기 (1) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1153'>C# 소스 코드를 스크립트 처럼 다루는 방법</a> Roslyn 맛보기 (2) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1154'>C# Interactive (1)</a> Roslyn 맛보기 (3) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1155'>C# Interactive (2)</a> Roslyn 맛보기 (4) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1156'>Roslyn Services APIs를 이용한 Code Issue 및 Code Action 기능 소개</a> Roslyn 맛보기 (5) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1157'>Syntax Analysis (Roslyn Syntax API)</a> Roslyn 맛보기 (6) - <a target='tab' href='http://www.sysnet.pe.kr/2/0/1158'>Roslyn Symbol / Binding API</a> Roslyn 맛보기 (7) - SyntaxTree 조작 </pre> <br /> 드디어 마지막 이야기까지 왔군요. ^^<br /> <br /> 이번 글은, "%PROGRAMFILES% (x86)\Microsoft Codename Roslyn CTP\Getting Started - Syntax Transformation (CSharp).docx" 파일(현재는 "<a target='tab' href='https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-transformation'>Get started with syntax transformation</a>" 자료로 제공)을 요약한 것이라고 보면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이전에 언급했던 것처럼, SyntaxTree는 immutable이므로 변경을 가하는 경우 새롭게 생성된 별개의 SyntaxTree 인스턴스가 반환이 됩니다. 그래서인지, 변경 메서드 자체도 set ... 류가 아닌 replace ... 류입니다.<br /> <br /> Replace를 해야 하기 때문에 교체될 노드를 새롭게 생성하는 방법이 필요한데요. 이러한 목적으로 Syntax API에서는 특정 SyntaxNode를 생성할 수 있는 Factory 메서드를 제공해 줍니다.<br /> <br /> 예를 들어, "using System.Collections.Generic" 선언을 위해 다음과 같은 작업들이 수반됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NameSyntax name = Syntax.IdentifierName("System"); name = Syntax.QualifiedName(left: name, right: Syntax.IdentifierName("Collections")); name = Syntax.QualifiedName(left: name, right: Syntax.IdentifierName("Generic")); Console.WriteLine(name.GetText()); // 출력 결과: System.Collections.Generic </pre> <br /> 마치 기존의 <a target='tab' href='https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/september/net-development-expression-trees-in-visual-basic-and-csharp'>Expression Tree</a>를 사용한 것과 방식이 유사합니다. (프로그래밍 언어의 구문 분석이 그러하니 비슷하겠지요. ^^)<br /> <br /> 자, 이렇게 '새로운 SyntaxNode'를 하나 생성했으면 변경이 필요한 기존 SyntaxNode를 지정하고 교체를 해야겠지요. 이를 위해서 다음의 예제코드에서처럼 Update / Replace 메서드를 제공하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SyntaxTree tree = SyntaxTree.ParseCompilationUnit( @"using System; <span style='color: blue; font-weight: bold'>using System.Collections;</span> using System.Linq; using System.Text; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine(""Hello, World!""); } } }"); var root = (CompilationUnitSyntax)tree.Root; var oldUsing = root.Usings[1]; <span style='color: blue; font-weight: bold'>var newUsing = oldUsing.Update(oldUsing.UsingKeyword, oldUsing.AliasOpt, name, oldUsing.SemicolonToken);</span> <span style='color: blue; font-weight: bold'>var modifiedRoot = root.ReplaceNode(oldUsing, newUsing);</span> Console.WriteLine(modifiedRoot.GetText()); // 출력 결과: using System; <span style='color: blue; font-weight: bold'>using System.Collections.Generic;</span> using System.Linq; using System.Text; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } } </pre> <br /> 아마도 예전 같았으면.... 간단하게 "using System.Collections;"을 문자열 검색으로 찾아서 "using System.Collections.Generic;"로 바꿨을 지도 모릅니다. 이제는 Roslyn이 나왔으니, C# 언어의 문법에 맞게 안정적으로 코드를 조작하는 것이 가능해 진 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 사실, 이런 유의 작업에서 대부분의 경우 공통적으로 SyntaxTree에 대해서 '탐색'하는 작업을 코드로 구현해야 할 텐데요. 물론, 사용자가 직접 만들어도 되지만 어찌 보면 이런 코드가 대체적으로 정형화된 패턴이기 때문에 마이크로소프트는 Rosyln에서 SyntaxRewriter 타입을 통해서 마치 XML의 SAX parser처럼 노드를 열람해 주는 기반을 제공해 주고 있습니다.<br /> <br /> 이 때문에, 개발자가 해주어야 하는 것은 단지 SyntaxRewriter를 상속받아서, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class MyRewriter : <span style='color: blue; font-weight: bold'>SyntaxRewriter</span> { } </pre> <br /> 원하는 '대체 노드' 후보자들이 열람되었을 때 알려주는 메서드를 override해서 구현 코드를 넣어주면 되는데, SyntaxRewriter는 다음과 같이 대부분의 구문 요소에 대해 override 함수들을 제공해 주고 있습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > protected internal override SyntaxNode VisitAccessorDeclaration(AccessorDeclarationSyntax node); protected internal override SyntaxNode VisitAccessorList(AccessorListSyntax node); protected internal override SyntaxNode VisitAliasQualifiedName(AliasQualifiedNameSyntax node); protected internal override SyntaxNode VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node); protected internal override SyntaxNode VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node); protected internal override SyntaxNode VisitArgument(ArgumentSyntax node); protected internal override SyntaxNode VisitArgumentList(ArgumentListSyntax node); protected internal override SyntaxNode VisitArrayCreationExpression(ArrayCreationExpressionSyntax node); protected internal override SyntaxNode VisitArrayRankSpecifier(ArrayRankSpecifierSyntax node); protected internal override SyntaxNode VisitArrayType(ArrayTypeSyntax node); protected internal override SyntaxNode VisitAttribute(AttributeSyntax node); protected internal override SyntaxNode VisitAttributeArgument(AttributeArgumentSyntax node); protected internal override SyntaxNode VisitAttributeArgumentList(AttributeArgumentListSyntax node); protected internal override SyntaxNode VisitAttributeDeclaration(AttributeDeclarationSyntax node); protected internal override SyntaxNode VisitAttributeTargetSpecifier(AttributeTargetSpecifierSyntax node); protected internal override SyntaxNode VisitBadDirective(BadDirectiveSyntax node); protected internal override SyntaxNode VisitBaseExpression(BaseExpressionSyntax node); protected internal override SyntaxNode VisitBaseList(BaseListSyntax node); protected internal override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node); protected internal override SyntaxNode VisitBlock(BlockSyntax node); protected internal override SyntaxNode VisitBracketedArgumentList(BracketedArgumentListSyntax node); protected internal override SyntaxNode VisitBracketedParameterList(BracketedParameterListSyntax node); protected internal override SyntaxNode VisitBreakStatement(BreakStatementSyntax node); protected internal override SyntaxNode VisitCastExpression(CastExpressionSyntax node); protected internal override SyntaxNode VisitCatchClause(CatchClauseSyntax node); protected internal override SyntaxNode VisitCatchDeclaration(CatchDeclarationSyntax node); protected internal override SyntaxNode VisitCheckedExpression(CheckedExpressionSyntax node); protected internal override SyntaxNode VisitCheckedStatement(CheckedStatementSyntax node); protected internal override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node); protected internal override SyntaxNode VisitClassOrStructConstraint(ClassOrStructConstraintSyntax node); protected internal override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node); protected internal override SyntaxNode VisitConditionalExpression(ConditionalExpressionSyntax node); protected internal override SyntaxNode VisitConstructorConstraint(ConstructorConstraintSyntax node); protected internal override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node); protected internal override SyntaxNode VisitConstructorInitializer(ConstructorInitializerSyntax node); protected internal override SyntaxNode VisitContinueStatement(ContinueStatementSyntax node); protected internal override SyntaxNode VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node); protected internal override SyntaxNode VisitDefaultExpression(DefaultExpressionSyntax node); protected internal override SyntaxNode VisitDefineDirective(DefineDirectiveSyntax node); protected internal override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node); protected internal override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node); protected internal override SyntaxNode VisitDocumentationComment(DocumentationCommentSyntax node); protected internal override SyntaxNode VisitDoStatement(DoStatementSyntax node); protected internal override SyntaxNode VisitElementAccessExpression(ElementAccessExpressionSyntax node); protected internal override SyntaxNode VisitElifDirective(ElifDirectiveSyntax node); protected internal override SyntaxNode VisitElseClause(ElseClauseSyntax node); protected internal override SyntaxNode VisitElseDirective(ElseDirectiveSyntax node); protected internal override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node); protected internal override SyntaxNode VisitEndIfDirective(EndIfDirectiveSyntax node); protected internal override SyntaxNode VisitEndRegionDirective(EndRegionDirectiveSyntax node); protected internal override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node); protected internal override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node); protected internal override SyntaxNode VisitEqualsValueClause(EqualsValueClauseSyntax node); protected internal override SyntaxNode VisitErrorDirective(ErrorDirectiveSyntax node); protected internal override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node); protected internal override SyntaxNode VisitEventFieldDeclaration(EventFieldDeclarationSyntax node); protected internal override SyntaxNode VisitExplicitInterfaceSpecifier(ExplicitInterfaceSpecifierSyntax node); protected internal override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node); protected internal override SyntaxNode VisitExternAliasDirective(ExternAliasDirectiveSyntax node); protected internal override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node); protected internal override SyntaxNode VisitFinallyClause(FinallyClauseSyntax node); protected internal override SyntaxNode VisitFixedStatement(FixedStatementSyntax node); protected internal override SyntaxNode VisitForEachStatement(ForEachStatementSyntax node); protected internal override SyntaxNode VisitForStatement(ForStatementSyntax node); protected internal override SyntaxNode VisitFromClause(FromClauseSyntax node); protected internal override SyntaxNode VisitGenericName(GenericNameSyntax node); protected internal override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node); protected internal override SyntaxNode VisitGotoStatement(GotoStatementSyntax node); protected internal override SyntaxNode VisitGroupClause(GroupClauseSyntax node); protected internal override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node); protected internal override SyntaxNode VisitIfDirective(IfDirectiveSyntax node); protected internal override SyntaxNode VisitIfStatement(IfStatementSyntax node); protected internal override SyntaxNode VisitImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node); protected internal override SyntaxNode VisitIncompleteMember(IncompleteMemberSyntax node); protected internal override SyntaxNode VisitIndexerDeclaration(IndexerDeclarationSyntax node); protected internal override SyntaxNode VisitInitializerExpression(InitializerExpressionSyntax node); protected internal override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node); protected internal override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node); protected internal override SyntaxNode VisitJoinClause(JoinClauseSyntax node); protected internal override SyntaxNode VisitJoinIntoClause(JoinIntoClauseSyntax node); protected internal override SyntaxNode VisitLabeledStatement(LabeledStatementSyntax node); protected internal override SyntaxNode VisitLetClause(LetClauseSyntax node); protected internal override SyntaxNode VisitLineDirective(LineDirectiveSyntax node); protected internal override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node); protected internal override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node); protected internal override SyntaxNode VisitLockStatement(LockStatementSyntax node); protected internal override SyntaxNode VisitMakeRefExpression(MakeRefExpressionSyntax node); protected internal override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node); protected internal override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node); protected internal override SyntaxNode VisitNameColon(NameColonSyntax node); protected internal override SyntaxNode VisitNameEquals(NameEqualsSyntax node); protected internal override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node); protected internal override SyntaxNode VisitNullableType(NullableTypeSyntax node); protected internal override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node); protected internal override SyntaxNode VisitOperatorDeclaration(OperatorDeclarationSyntax node); protected internal override SyntaxNode VisitOrderByClause(OrderByClauseSyntax node); protected internal override SyntaxNode VisitOrdering(OrderingSyntax node); protected internal override SyntaxNode VisitParameter(ParameterSyntax node); protected internal override SyntaxNode VisitParameterList(ParameterListSyntax node); protected internal override SyntaxNode VisitParenthesizedExpression(ParenthesizedExpressionSyntax node); protected internal override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node); protected internal override SyntaxNode VisitPointerType(PointerTypeSyntax node); protected internal override SyntaxNode VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node); protected internal override SyntaxNode VisitPragmaChecksumDirective(PragmaChecksumDirectiveSyntax node); protected internal override SyntaxNode VisitPragmaWarningDirective(PragmaWarningDirectiveSyntax node); protected internal override SyntaxNode VisitPredefinedType(PredefinedTypeSyntax node); protected internal override SyntaxNode VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node); protected internal override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node); protected internal override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node); protected internal override SyntaxNode VisitQueryContinuation(QueryContinuationSyntax node); protected internal override SyntaxNode VisitQueryExpression(QueryExpressionSyntax node); protected internal override SyntaxNode VisitReferenceDirective(ReferenceDirectiveSyntax node); protected internal override SyntaxNode VisitRefTypeExpression(RefTypeExpressionSyntax node); protected internal override SyntaxNode VisitRefValueExpression(RefValueExpressionSyntax node); protected internal override SyntaxNode VisitRegionDirective(RegionDirectiveSyntax node); protected internal override SyntaxNode VisitReturnStatement(ReturnStatementSyntax node); protected internal override SyntaxNode VisitSelectClause(SelectClauseSyntax node); protected internal override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node); protected internal override SyntaxNode VisitSizeOfExpression(SizeOfExpressionSyntax node); protected internal override SyntaxNode VisitSkippedTokens(SkippedTokensSyntax node); protected internal override SyntaxNode VisitStackAllocArrayCreationExpression(StackAllocArrayCreationExpressionSyntax node); protected internal override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node); protected internal override SyntaxNode VisitSwitchLabel(SwitchLabelSyntax node); protected internal override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node); protected internal override SyntaxNode VisitSwitchStatement(SwitchStatementSyntax node); protected internal override SyntaxNode VisitThisExpression(ThisExpressionSyntax node); protected internal override SyntaxNode VisitThrowStatement(ThrowStatementSyntax node); protected internal override SyntaxNode VisitTryStatement(TryStatementSyntax node); protected internal override SyntaxNode VisitTypeArgumentList(TypeArgumentListSyntax node); protected internal override SyntaxNode VisitTypeConstraint(TypeConstraintSyntax node); protected internal override SyntaxNode VisitTypeOfExpression(TypeOfExpressionSyntax node); protected internal override SyntaxNode VisitTypeParameter(TypeParameterSyntax node); protected internal override SyntaxNode VisitTypeParameterConstraintClause(TypeParameterConstraintClauseSyntax node); protected internal override SyntaxNode VisitTypeParameterList(TypeParameterListSyntax node); protected internal override SyntaxNode VisitUndefDirective(UndefDirectiveSyntax node); protected internal override SyntaxNode VisitUnsafeStatement(UnsafeStatementSyntax node); protected internal override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node); protected internal override SyntaxNode VisitUsingStatement(UsingStatementSyntax node); protected internal override SyntaxNode VisitVariableDeclaration(VariableDeclarationSyntax node); protected internal override SyntaxNode VisitVariableDeclarator(VariableDeclaratorSyntax node); protected internal override SyntaxNode VisitWarningDirective(WarningDirectiveSyntax node); protected internal override SyntaxNode VisitWhereClause(WhereClauseSyntax node); protected internal override SyntaxNode VisitWhileStatement(WhileStatementSyntax node); protected internal override SyntaxNode VisitXmlAttribute(XmlAttributeSyntax node); protected internal override SyntaxNode VisitXmlCDataSection(XmlCDataSectionSyntax node); protected internal override SyntaxNode VisitXmlComment(XmlCommentSyntax node); protected internal override SyntaxNode VisitXmlElement(XmlElementSyntax node); protected internal override SyntaxNode VisitXmlElementEndTag(XmlElementEndTagSyntax node); protected internal override SyntaxNode VisitXmlElementStartTag(XmlElementStartTagSyntax node); protected internal override SyntaxNode VisitXmlEmptyElement(XmlEmptyElementSyntax node); protected internal override SyntaxNode VisitXmlName(XmlNameSyntax node); protected internal override SyntaxNode VisitXmlPrefix(XmlPrefixSyntax node); protected internal override SyntaxNode VisitXmlProcessingInstruction(XmlProcessingInstructionSyntax node); protected internal override SyntaxNode VisitXmlText(XmlTextSyntax node); protected internal override SyntaxNode VisitYieldStatement(YieldStatementSyntax node); </pre> <br /> "<a target='tab' href='https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-transformation'>Getting Started - Syntax Transformation (CSharp)</a>" 문서에 실린 예제에서는 로컬 변수 선언에 대해서 'var' 키워드로 바꾸는 것이 가능한 경우를 찾아서 해당 키워드로 대체해 주고 있기 때문에 Visit<span style='color: blue; font-weight: bold'>LocalDeclaration</span>Statement 메서드를 재정의하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class TypeInferenceRewriter : SyntaxRewriter { private readonly SemanticModel SemanticModel; public TypeInferenceRewriter(SemanticModel semanticModel) { this.SemanticModel = semanticModel; } protected override SyntaxNode <span style='color: blue; font-weight: bold'>VisitLocalDeclarationStatement</span>( LocalDeclarationStatementSyntax node) { if (node.Declaration.Variables.Count > 1) { return node; } if (node.Declaration.Variables[0].InitializerOpt == null) { return node; } VariableDeclaratorSyntax declarator = node.Declaration.Variables.First(); TypeSyntax variableTypeName = node.Declaration.Type; TypeSymbol variableType = SemanticModel.GetSemanticInfo(variableTypeName).Type; SemanticInfo initializerInfo = SemanticModel.GetSemanticInfo(declarator.InitializerOpt.Value); if (variableType == initializerInfo.Type) { TypeSyntax varTypeName = Syntax.IdentifierName("var") .WithLeadingTrivia(variableTypeName.GetLeadingTrivia()) .WithTrailingTrivia(variableTypeName.GetTrailingTrivia()); return <span style='color: blue; font-weight: bold'>node.ReplaceNode(variableTypeName, varTypeName);</span> } else { return node; } } } </pre> <br /> C# 구문을 해석하는 것을 넘어서, 이제는 문법에 맞게 변경도 할 수 있게 되었군요. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1459
(왼쪽의 숫자를 입력해야 합니다.)