В предыдущем посте я начал вести блог о том, как Xtext 2.1 чрезвычайно облегчил мощную интеграцию с Java в вашем DSL. В этой статье я хотел бы продолжить экспериментировать с этим механизмом, и в частности я начну использовать Xbase , новую библиотеку языка выражений, которая позволяет интегрировать (Java-подобные) выражения в ваш DSL.
Если вы посмотрите на Пять простых шагов к языку JVM , вы увидите мощь Xbase! Более того, в Xtext 2.1 использование Xbase еще проще, поскольку, написав только AbstractModelInferrer, вы получите полную интеграцию с Java (путем сопоставления элементов вашей модели AST с концепциями Java) и генератор (который создаст код Java, соответствующий этому отображению). ). Таким образом, вам даже не понадобится генератор вообще.
Тем не менее, в этом посте я хотел бы проверить, как использовать только небольшую часть Xbase и при этом иметь контроль над частью генерации: в частности (для других проектов) я хотел бы сохранить контроль над генерацией для моей модель, полагаясь на генерацию Xbase для деталей Xbase. Таким образом, в этом посте я опишу:
- как интегрировать выражения Xbase (XExpression) в ваш DSL
- написать генератор для вашего DSL и повторно использовать XbaseCompiler для кода XExpressions
Следуя духу предыдущего поста , я не буду использовать пример Domainmodel, но что-то действительно простое, например пример Greeting, т. Е. Базовый DSL, который вы получите при создании проекта Xtext внутри Eclipse. В частности, вы можете захотеть просмотреть предыдущий пост перед прочтением этого поста (поскольку мы повторно используем некоторые из представленных здесь концепций).
Итак, прежде всего, создайте новый проект Xtext; Я назову этот проект org.xtext.example.helloxbase (и файлы будут иметь расширение helloxbase ). Теперь мы можем начать генерировать артефакты Xtext. С настройками по умолчанию вы также получите HelloXbaseGenerator.xtend , который мы сейчас генерируем для кода Java. Первая реализация генератора: для каждого элемента Greeting мы получим пакет и класс, названный в соответствии с функцией имени Greeting (упаковывать все строчные буквы и имя класса с заглавной буквой). Кроме того, сгенерированный метод main выведет соответствующее приветствие:
package org.xtext.example.helloxbase.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator
import org.xtext.example.helloxbase.helloXbase.Greeting
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*
class HelloXbaseGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
for(greeting: resource.allContentsIterable.filter(typeof(Greeting))) {
fsa.generateFile(
greeting.packageName + "/" + // package
greeting.className + ".java", // class name
greeting.compile)
}
}
def compile(Greeting greeting) '''
package «greeting.packageName»;
public class «greeting.className» {
public static void main(String args[]) {
System.out.println("Hello «greeting.name»");
}
}
'''
def className(Greeting greeting) {
greeting.name.toFirstUpper
}
def packageName(Greeting greeting) {
greeting.name.toLowerCase
}
}
Если вы уже знакомы с Xtend2 (или читали предыдущий пост), вы должны иметь представление о том, что делает генератор; давайте попробуем генератор: перезапустите ваш второй экземпляр eclipse и убедитесь, что в проекте, который вы создали с помощью файла helloxbase, у вас есть папка с именем src-gen , и эта папка настроена как исходная папка. Если вы наберете что-то вроде
Hello foo! Hello bar!
Вы должны увидеть некоторый код Java, сгенерированный в папке src-gen (согласно схеме, описанной выше).
Теперь идея состоит в том, чтобы обогатить приветствующий DSL HelloXbase некоторыми выражениями Xbase, поэтому мы изменим грамматику языка (HelloXbase.xtext) следующим образом (вы должны быть знакомы с функциями импорта Xtext):
grammar org.xtext.example.helloxbase.HelloXbase with
org.eclipse.xtext.xbase.Xbase
generate helloXbase "http://www.xtext.org/example/helloxbase/HelloXbase"
Model:
imports += Import*
greetings+=Greeting*;
Import:
'import' importedNamespace = QualifiedNameWithWildcard
;
QualifiedNameWithWildcard:
QualifiedName '.*'?
;
Greeting:
'Hello' name=ID 'from' expression = XExpression '!'
;
Обратите внимание, что теперь мы получаем нашу грамматику из грамматики Xbase, поэтому теперь мы можем полагаться на правила грамматики Xbase; идея состоит в том, чтобы иметь возможность написать в нашем DSL что-то вроде

Вы можете увидеть много интересных вещей здесь! В основном у вас есть весь мощный Java-подобный синтаксис выражений Xbase, доступ к статическим методам с использованием ::, замыканий и методов расширения ( nullOrEmpty из StringExtensions )! Все бесплатно!
Теперь, что случилось с нашим генератором? Это все еще вызывается автоматически при изменении ресурса? Боюсь, что нет: как только вы вывели свою грамматику из Xbase, в вашем проекте появился новый файл HelloXbaseJvmModelInferrer.xtend , поскольку в Xtext 2.1, как было указано в начале, стратегия генерации изменилась. В частности, в src-gen AbstractHelloXbaseRuntimeModule IGenerator теперь связан с org.eclipse.xtext.xbase.compiler.JvmModelGenerator ; так как мы хотим вернуть наш собственный генератор, мы просто переопределяем эту привязку в HelloXbaseRuntimeModule (той, которую нам разрешено изменять):
public class HelloXbaseRuntimeModule extends org.xtext.example.helloxbase.AbstractHelloXbaseRuntimeModule {
/**
* Avoids to use the default org.eclipse.xtext.xbase.compiler.JvmModelGenerator
* when using xbase.
* @see org.xtext.example.helloxbase.AbstractHelloXbaseRuntimeModule#bindIGenerator()
*/
@Override
public Class<? extends IGenerator> bindIGenerator() {
return HelloXbaseGenerator.class;
}
}
Если вы перезапустите свой экземпляр времени выполнения, вы увидите, что наш генератор вернулся!
Теперь мы хотим сгенерировать код Java, соответствующий Xbase XExpression; Затем мы просто повторно используем XbaseCompiler в нашем генераторе (что, как вы знаете, в Xtext означает «впрыскивать его»). В генераторе мы теперь будем использовать также ImportManager (который объясняется в предыдущем посте ) и начнем проводить некоторые эксперименты с XbaseCompiler:
package org.xtext.example.helloxbase.generator
import com.google.inject.Inject
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.common.types.TypesFactory
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.xbase.XExpression
import org.eclipse.xtext.xbase.compiler.ImportManager
import org.eclipse.xtext.xbase.compiler.StringBuilderBasedAppendable
import org.eclipse.xtext.xbase.compiler.XbaseCompiler
import org.xtext.example.helloxbase.helloXbase.Greeting
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*
class HelloXbaseGenerator implements IGenerator {
@Inject
protected XbaseCompiler xbaseCompiler
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
for(greeting: resource.allContentsIterable.filter(typeof(Greeting))) {
fsa.generateFile(
greeting.packageName + "/" + // package
greeting.className + ".java", // class name
greeting.compile)
}
}
def getJvmType(Greeting greeting) {
val declaredType = TypesFactory::eINSTANCE.createJvmGenericType
declaredType.setSimpleName(greeting.className)
declaredType.setPackageName(greeting.packageName)
declaredType
}
def compile(Greeting greeting) '''
«val importManager = new ImportManager(true, getJvmType(greeting))»
«val mainMethod = compile(greeting, importManager)»
package «greeting.packageName»;
«IF !importManager.imports.empty»
«FOR i : importManager.imports»
import «i»;
«ENDFOR»
«ENDIF»
«mainMethod»
'''
def compile(Greeting greeting, ImportManager importManager) '''
public class «greeting.className» {
public static void main(String args[]) {
«compile(greeting.expression, importManager)»
System.out.println("Hello «greeting.name» from ");
}
}
'''
def compile(XExpression xExpression, ImportManager importManager) {
val result = new StringBuilderBasedAppendable(importManager)
xbaseCompiler.toJavaStatement(xExpression, result, true)
result
}
def className(Greeting greeting) {
greeting.name.toFirstUpper
}
def packageName(Greeting greeting) {
greeting.name.toLowerCase
}
}
Давайте сосредоточимся на этих частях:
def compile(Greeting greeting, ImportManager importManager) '''
public class «greeting.className» {
public static void main(String args[]) {
«compile(greeting.expression, importManager)»
System.out.println("Hello «greeting.name» from ");
}
}
'''
def compile(XExpression xExpression, ImportManager importManager) {
val result = new StringBuilderBasedAppendable(importManager)
xbaseCompiler.toJavaStatement(xExpression, result, true)
result
}
Мы попытались использовать toJavaStatement из XbaseCompiler, который кажется хорошим кандидатом на то, что мы хотим; в частности, передавая true в качестве последнего аргумента, мы говорим, что «результат» генерации должен быть присвоен переменной Java, чтобы мы могли ссылаться на нее; давайте посмотрим, что происходит в рабочей среде времени выполнения с этим поколением:

Ну, класс StringBuilderBasedAppendable не только содержит результат генерации; он также содержит карту со всеми промежуточными локальными переменными Java, сгенерированными компилятором. Каждая переменная на карте имеет в качестве ключа соответствующий объект Xbase. Таким образом, нам нужно знать только имя переменной, соответствующее нашему выражению XExpression, и все готово:
result.getName(greeting.expression)
Итак, финальный генератор выглядит так:
package org.xtext.example.helloxbase.generator
import com.google.inject.Inject
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.common.types.TypesFactory
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.xbase.XExpression
import org.eclipse.xtext.xbase.compiler.ImportManager
import org.eclipse.xtext.xbase.compiler.StringBuilderBasedAppendable
import org.eclipse.xtext.xbase.compiler.XbaseCompiler
import org.xtext.example.helloxbase.helloXbase.Greeting
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*
class HelloXbaseGenerator implements IGenerator {
@Inject
protected XbaseCompiler xbaseCompiler
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
for(greeting: resource.allContentsIterable.filter(typeof(Greeting))) {
fsa.generateFile(
greeting.packageName + "/" + // package
greeting.className + ".java", // class name
greeting.compile)
}
}
def getJvmType(Greeting greeting) {
val declaredType = TypesFactory::eINSTANCE.createJvmGenericType
declaredType.setSimpleName(greeting.className)
declaredType.setPackageName(greeting.packageName)
declaredType
}
def compile(Greeting greeting) '''
«val importManager = new ImportManager(true, getJvmType(greeting))»
«val mainMethod = compile(greeting, importManager)»
package «greeting.packageName»;
«IF !importManager.imports.empty»
«FOR i : importManager.imports»
import «i»;
«ENDFOR»
«ENDIF»
«mainMethod»
'''
def compile(Greeting greeting, ImportManager importManager) '''
«val result = compile(greeting.expression, importManager)»
public class «greeting.className» {
public static void main(String args[]) {
«result»
Object expression = «result.getName(greeting.expression)»;
System.out.println("Hello «greeting.name» from " +
expression.toString());
}
}
'''
def compile(XExpression xExpression, ImportManager importManager) {
val result = new StringBuilderBasedAppendable(importManager)
xbaseCompiler.toJavaStatement(xExpression, result, true)
result
}
def className(Greeting greeting) {
greeting.name.toFirstUpper
}
def packageName(Greeting greeting) {
greeting.name.toLowerCase
}
}
И во время выполнения вы получите то, что хотели:
Вы можете найти источники для проекта helloxbase на
https://github.com/LorenzoBettini/Xtext2-experiments
Надеюсь, вы найдете этот пост полезным и следите за новыми сообщениями о Xtext ?
