Archive

Posts Tagged ‘Cross-Reference’

Xtext and Strings as Cross-References

Cross References are a often used concept in Xtext. They ususally work like this

Model:
    definitions+=Definition*
    usages+=Usage*
;

Definition:
    "define" name=ID
;

Usage:
   "use" definition=[Definition]
;

They can be used in the model like this

define Thing
use Thing

but what if i want to write something like

define "This is a Thing"
use "This is a Thing"

well the definition part is easily changed

Definition:
    "define" name=STRING
;

But what about the usage part?
well it is quite easy as well. refName=[Type] is short for refName=[Type|ID] which means ‘Refererence a Type and parse an ID. So to use another Terminal or Data Type Rule we change it to refName=[Type|RULENAME]

Usage:
   "use" definition=[Definition|STRING]
;

Now the cross refs are working fine. but if we try the editor we find out what autoedit and content assist disturb each other. We type " and auto edit gets us to "|". If we now type Crtl+Space for content assist we finally get "This is a Thing"" with an extra " at the end.
To avoid this we have to tweak the proposal provider a bit.

package org.xtext.example.mydsl.ui.contentassist

import org.xtext.example.mydsl.ui.contentassist.AbstractMyDslProposalProvider
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.Assignment
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor.Delegate
import org.eclipse.jface.text.contentassist.ICompletionProposal
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal

class MyDslProposalProvider extends AbstractMyDslProposalProvider {

	override completeUsage_Definition(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
		super.completeUsage_Definition(model, assignment, context, new  StringProposalDelegate(acceptor, context))
	}

	static class StringProposalDelegate extends Delegate {

		ContentAssistContext ctx

		new(ICompletionProposalAcceptor delegate, ContentAssistContext ctx) {
			super(delegate)
			this.ctx = ctx
		}

		override accept(ICompletionProposal proposal) {
			if (proposal instanceof ConfigurableCompletionProposal) {
				val endPos = proposal.replacementOffset + proposal.replacementLength 
				if (ctx.document != null && ctx.document.length > endPos) {
					// We are not at the end of the file
					if ("\"" == ctx.document.get(endPos, 1)) {
						proposal.replacementLength = proposal.replacementLength-1
						proposal.replacementString = proposal.replacementString.substring(0,proposal.replacementString.length-1)
					}
				}
			}
			super.accept(proposal)
		}

	}

}

what we basically do is detecting the situation and remove the extra "

Xtext: Referencing Elements of one DSL from another DSL

This is a blog post on Inter-Language-Cross-References in Xtext. Let us asume we have a DSL that contains Definitions. Now we want to create another DSL that references (uses) the Definitions defined in the the first DSL.

So let us start with the first DSL: We create a new Xtext Project

And here is the (for demonstration purpose oversimplyfied) Grammar

grammar org.xtext.example.definitions.Definitions with org.eclipse.xtext.common.Terminals

generate definitions "http://www.xtext.org/example/definitions/Definitions"

Model:
	definitions+=Definition*;

Definition:
	'define' name=ID;

We run GenerateDefinitions.mwe2 to generate the language.
This is all for the first DSL.

Now let us create a project for the second DSL.

To be able to reference the first DSL from the second we add a bundle dependency to the Manifest of the second

Here the resulting manifest

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: org.xtext.example.usages
Bundle-Vendor: My Company
Bundle-Version: 1.0.0.qualifier
Bundle-SymbolicName: org.xtext.example.usages; singleton:=true
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.xtext;visibility:=reexport,
 org.eclipse.xtext.xbase;resolution:=optional;visibility:=reexport,
 org.eclipse.xtext.generator;resolution:=optional,
 org.apache.commons.logging;bundle-version="1.0.4";resolution:=optional,
 org.eclipse.emf.codegen.ecore;resolution:=optional,
 org.eclipse.emf.mwe.utils;resolution:=optional,
 org.eclipse.emf.mwe2.launch;resolution:=optional,
 org.xtext.example.definitions;bundle-version="1.0.0"
Import-Package: org.apache.log4j
Bundle-RequiredExecutionEnvironment: J2SE-1.5

Now we can create the Grammar from the Usage DSL

grammar org.xtext.example.usages.Usages with org.eclipse.xtext.common.Terminals

generate usages "http://www.xtext.org/example/usages/Usages"

import "http://www.xtext.org/example/definitions/Definitions" as def

Model:
	usages+=Usage*;
	
Usage:
	'use' definition=[def::Definition];

With "http://www.xtext.org/example/definitions/Definitions" as def we import the metamodel of our Define DSL to our Usage DSL.
Thus the Type Definition is available and can be used to define the cross reference definition=[def::Definition]

To get the thing running we have to do some adjustments to the workflow of the Usages language.

module org.xtext.example.usages.GenerateUsages

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.generator.*
import org.eclipse.xtext.ui.generator.*

var grammarURI = "classpath:/org/xtext/example/usages/Usages.xtext"
var file.extensions = "use"
var projectName = "org.xtext.example.usages"
var runtimeProject = "../${projectName}"

Workflow {
    bean = StandaloneSetup {
        scanClassPath = true
        platformUri = "${runtimeProject}/..";
        registerGeneratedEPackage = "org.xtext.example.definitions.definitions.DefinitionsPackage"
        registerGenModelFile = "platform:/resource/org.xtext.example.definitions/src-gen/org/xtext/example/definitions/Definitions.genmodel"
    }
    
        ...
}

we generate our Usage DSL (GenerateUsages.mwe2)

finally we start a new runtime eclipse and give it a try

Update Xtext 2.9.2+:

Here is how the workflow looks like with the new Xtext 2.9.x generator

module org.xtext.example.usages.GenerateUsages

import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*

var rootPath = ".."

Workflow {
	
	component = XtextGenerator {
		configuration = {
			project = StandardProjectConfig {
				baseName = "org.xtext.example.usages"
				rootPath = rootPath
				runtimeTest = {
					enabled = true
				}
				eclipsePlugin = {
					enabled = true
				}
				eclipsePluginTest = {
					enabled = true
				}
				createEclipseMetaData = true
			}
			code = {
				encoding = "UTF-8"
				fileHeader = "/*\n * generated by Xtext \${version}\n */"
			}
		}
		language = StandardLanguage {
			name = "org.xtext.example.usages.Usages"
			referencedResource = "platform:/resource/org.xtext.example.definitions/model/generated/Definitions.genmodel"
			fileExtensions = "use"

			serializer = {
				generateStub = false
			}
			validator = {
			}
		}
	}
}