基于Xtext开发Java编辑器:实现方法调用替换为实际代码
Hey there! Let's walk through how to implement this method inlining feature with Xtext, building on the lexer/parser work you've already done. Here's a step-by-step solution tailored to your use case:
1. Validate & Refine Your Xtext Grammar
First, make sure your grammar explicitly captures method declarations and method calls so the AST can distinguish them clearly. For your Java-like syntax, you might have rules like this in your .xtext file:
ClassDeclaration: 'Class' name=ID '{' members+=ClassMember* '}'; ClassMember: MethodDeclaration | MainMethodDeclaration; MainMethodDeclaration: 'public' 'static' 'void' 'main' '(' 'String' '[' ']' args=ID ')' block=Block; MethodDeclaration: 'public' 'static' type=VoidType? name=ID '(' ')' block=Block; VoidType: 'void'; Block: '{' statements+=Statement* '}'; Statement: PrintStatement | MethodCall; PrintStatement: 'System.out.println' '(' string=STRING ')'; MethodCall: name=ID '(' ')';
This ensures the parser creates distinct nodes for MethodDeclaration and MethodCall, which is critical for later steps.
2. Build a Symbol Table to Link Calls to Definitions
You need a way to map each MethodCall node to its corresponding MethodDeclaration. Xtext's scope system can handle this:
- Create a custom
AbstractDeclarativeScopeProviderto provide scopes for method call names, pointing to method declarations in the same class. - Example snippet:
class MyScopeProvider extends AbstractDeclarativeScopeProvider { def IScope scope_MethodCall_name(MethodCall call, EReference ref) { val classDecl = call.eContainer.eContainer as ClassDeclaration Scopes.scopeFor(classDecl.members.filter(MethodDeclaration)) } }
This lets you resolve any MethodCall to its matching MethodDeclaration later on.
3. Implement the Inlining Logic
You have two main options depending on whether you want to generate transformed code or edit the live editor content:
Option A: Generate Transformed Code (Using IGenerator)
If you want to output the inlined code as a new file, use Xtext's generator framework:
class MyJavaGenerator extends AbstractGenerator { override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { val classDecl = resource.contents.head as ClassDeclaration fsa.generateFile(classDecl.name + "_inlined.java", generateInlinedClass(classDecl)) } def String generateInlinedClass(ClassDeclaration clazz) { '''Class «clazz.name» { «FOR member : clazz.members» «IF member instanceof MainMethodDeclaration» «generateInlinedMain(member as MainMethodDeclaration, clazz)» «ENDIF» «ENDFOR» }''' } def String generateInlinedMain(MainMethodDeclaration mainMethod, ClassDeclaration clazz) { '''public static void main(String[] args ){ «FOR stmt : mainMethod.block.statements» «IF stmt instanceof MethodCall» «val targetMethod = resolveMethod(stmt as MethodCall, clazz)» «FOR bodyStmt : targetMethod.block.statements» «generateStatement(bodyStmt)» «ENDFOR» «ELSE» «generateStatement(stmt)» «ENDIF» «ENDFOR» }''' } def MethodDeclaration resolveMethod(MethodCall call, ClassDeclaration clazz) { clazz.members.filter(MethodDeclaration).findFirst[ name == call.name ] } def String generateStatement(Statement stmt) { switch stmt { case PrintStatement: '''System.out.println("«stmt.string»"); ''' default: "" } } }
Option B: Live Editor Inlining (Using Quick Fix/Editor Action)
If you want to replace the method call in the active editor, create a custom quick fix or editor action:
- Use
XtextResourceto access the AST andReplaceEditto swap the method call text with the method body content. - Example snippet for a quick fix:
class InlineMethodQuickFix extends AbstractQuickFix { @Fix(MyIssueCodes.METHOD_CALL) def void inlineMethod(Issue issue, IssueResolutionAcceptor acceptor) { acceptor.accept(issue, "Inline method call", "Replace method call with its body", null) { ctx | val call = ctx.semanticElement as MethodCall val method = resolveMethod(call) val callRange = ctx.getRange(call) val bodyText = generateMethodBodyText(method) ctx.addEdit(ReplaceEdit.create(callRange, bodyText)) } } }
4. Handle Edge Cases
Don't forget to account for these details:
- Recursive calls: Add a check to avoid infinite inlining if a method calls itself.
- Method parameters: If your methods ever take arguments, you'll need to map call arguments to method parameters and replace parameter references in the body.
- Visibility rules: Ensure you only inline methods that are accessible from the call site (your example uses static methods, so this is simpler).
5. Test with Your Example
Run your generator or quick fix on the sample code you provided—you should see the method() call replaced directly with System.out.println("Purnam"); in the main method, just like your desired output.
内容的提问来源于stack exchange,提问作者Virag Purnam

