Xtend local variable scope? [message #1171811] |
Tue, 05 November 2013 08:56  |
Eclipse User |
|
|
|
I am developing my second grammar in 2 months!
But as usual I have a problem.
The goal is very simple. I wish to summarize a menu structure by listing the menu heading and then the list of elements that are supposed to be in that menu.
E.g.
@MENU_HEADER "File"
@MENU_ITEM "New" fileNew
@MENU_ITEM "Open" fileOpen
@MENU_ITEM "Close" fileClose
@MENU_HEADER "Edit"
@MENU_ITEM "Cut" editCut
@MENU_ITEM "Copy" editCopy
@MENU_ITEM "Paste" editPaste
So what this means (in my approach) is that the @MENU_HEADER method, in addition to creating template-based Java Swing code, should also create a local variable (in the Xtend file's scope) that is used subsequently by each @MENU_ITEM method. Note that in this example, the relationship between the MENU_HEADER name, the MENU_ITEM name, and the MENU_ITEM class, is purely coincidental. The MENU_ITEM class could be anything (although it does need to refer to a class that is actually in the project).
Here's what I tried:
«val menuName = "testing"»
«FOR Element m : mn.elements»
«switch m {
MENU_HEADER:{menuName = genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuName)}
}»
«ENDFOR»
}
'''
def String genMenu(MENU_HEADER m) '''
«val menuNameX = "mn" + m.name.replaceAll("\\s+","")»
JMenu «menuNameX» = new JMenu("«m.name»");
menuBar.add(«menuNameX»);
«return menuNameX»
'''
def genMenuItem(MENU_ITEM m, String menuNameX) '''
final JMenuItem mntm«m.className» = new JMenuItem("«m.name»");
mntm«m.className».addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SpinCADBlock pcB = new «m.className»CADBlock(50, 100);
dropBlock(panel, pcB);
}
});
«menuNameX».add(mntm«m.className»);
'''
On this line:
MENU_HEADER:{menuName = genMenu(m)}
I get this error: Assignment to final variable
and on this line:
I get Unreachable code
The grammar, code generator, and sample DSL file are attached as a ZIP.
Thanks for any insight!
GW
[Updated on: Tue, 05 November 2013 09:16] by Moderator
|
|
|
|
|
|
Re: Xtend local variable scope? [message #1173364 is a reply to message #1172910] |
Wed, 06 November 2013 08:19   |
Eclipse User |
|
|
|
Hi Sven,
Thanks for your response.
I don't want to generate a return statement in my Java output. I want the Xtend generated menu name to be returned so that I can use it repeatedly in my following template code.
For example, given this:
@MENU_HEADER "Control"
@MENU_ITEM "Sin/Cos LFO" SinCosLFO
@MENU_ITEM "Ramp LFO" RampLFO
I want this to come out in my generated Java:
JMenu mnControl = new JMenu("Control");
menuBar.add(mnControl);
JMenuItem mntmLFO = new JMenuItem("Sin/Cos LFO");
mntmLFO.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
SpinCADBlock pcB = new SinCosLFOCADBlock(50, 100);
dropBlock(panel, pcB);
pb.update();
}
});
mnControl.add(mntmLFO);
JMenuItem mntmRampLFO = new JMenuItem("Ramp LFO");
mntmRampLFO.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
SpinCADBlock pcB = new RampLFOCADBlock(50, 100);
dropBlock(panel, pcB);
pb.update();
}
});
mnControl.add(mntmRampLFO);
I have rearranged a few things and am very close, however I have a new problem.
def String genMenu(MENU_HEADER m) {
var menuNameX = "mn_" + m.name.replaceAll("\\s+","")
// '''
// JMenu «menuNameX» = new JMenu("«m.name»");
// menuBar.add(«menuNameX»);
// '''
menuNameX.toLowerCase()
}
This section works OK as far as returning the generated menu name. Unfortunately, if I uncomment the middle 4 lines, I get an error on the first ''' which says:
This expression is not allowed in this context, since it doesn't cause any side effects.
Also, the return value menuNameX.toLowerCase() is still getting inserted into my Java code, even though it's outside of the template section delimiters in genMenu. Perhap's that's getting emitted by the calling code?
«var menuName = "testing"»
«FOR Element m : mn.elements»
«switch m {
MENU_HEADER:{menuName = genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuName)}
}»
«ENDFOR»
I looked at the given Xtend example:
def toText(Node n) {
switch n {
Contents : n.text
A : '''<a href="«n.href»">«n.applyContents»</a>'''
default : '''
<«n.tagName»>
«n.applyContents»
</«n.tagName»>
'''
}
} and thought I was doing something roughly equivalent, but I guess not?
Thanks for your help.
GW
[Updated on: Wed, 06 November 2013 09:46] by Moderator
|
|
|
|
|
|
|
|
|
|
Re: Xtend local variable scope? [message #1175084 is a reply to message #1174190] |
Thu, 07 November 2013 09:14   |
Eclipse User |
|
|
|
Well, I solved my problem, which was to just go back and add the name of the MENU_HEADER to each MENU_ITEM, like this:
@menu "Wave Shaper"
@menuitem "Bit Crusher" BitCrusher "Wave Shaper"
@menuitem "Distortion" Distortion "Wave Shaper"
@menuitem "Gain" Gain "Wave Shaper"
@menu "Modulation"
@menuitem "GA Demo Phaser" ga_demo_phaser "Modulation"
@menuitem "GA Demo Flanger" ga_demo_flanger "Modulation"
@menuitem "GA Demo Wah" ga_demo_wah "Modulation"
It seems somewhat stupid and redundant to do this, but let me pause. I am trying to create a hierarchy without explicitly representing that hierarchy in my input data. I wanted there to be a "state" within my Xtend code generator (the name of the current MENU_HEADER) and whatever MENU_ITEMS were declared, would be associated with that MENU_HEADER until such time that a new MENU_HEADER was declared. Either Xtend doesn't like this approach or I am currently too dense to devise a workable solution.
Given that I have a workable (although slightly annoying) solution, I am going to move forward with it. I welcome any comments about appropriate software design, alternative tools that might be better, or whatever you like. I do have half a mind to (eventually) look at something like the ECore diagram generator, where it would be more straightforward to allow a non-programmer to easily create a hierarchical menu definition.
[Updated on: Thu, 07 November 2013 09:15] by Moderator
|
|
|
|
|
Re: Xtend local variable scope? [message #1176760 is a reply to message #1171811] |
Fri, 08 November 2013 09:39  |
Eclipse User |
|
|
|
OK I figured it out! Thanks Uli for the clue.
Here's the fundamental difference:
Before:
«FOR Element m : mn.elements»
«switch m {
MENU_HEADER:{menuNameY = genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuNameY)}
}»
«ENDFOR»
After:
«FOR Element m : mn.elements»
«switch m {
MENU_HEADER:{menuNameY = getMenuName(m.name); genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuNameY)}
}»
«ENDFOR»
So, rather than getting the menu name from genMenu(), I created a different method getMenuName() to just get the menu name. And I suppressed the extra menu name being generated in the code by creating a compound statement within the switch, then making the retrieval of the menu name the first of the two statements. Only the final statement is considered the return of the switch case (it appears).
/*
* generated by Xtext
*/
package com.holycityaudio.spincad.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import com.holycityaudio.spincad.spinCADMenu.Menu
import com.holycityaudio.spincad.spinCADMenu.MENU_ITEM
import com.holycityaudio.spincad.spinCADMenu.MENU_HEADER
import com.holycityaudio.spincad.spinCADMenu.Element
/**
* Generates code from your model files on save.
*
* see http://www.eclipse.org/Xtext/documentation.html#TutorialCodeGeneration
*/
class SpinCADMenuGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
var pkage = "\\com\\holycityaudio\\SpinCAD\\"
fsa.generateFile(pkage + resource.className+"Menu"+".java", toMenuCode(resource.contents.head as Menu))
}
def className(Resource res) {
var name = res.URI.lastSegment
println(name)
return name.substring(0, name.indexOf('.'))
}
def toMenuCode(Menu mn) {
var menuNameY = "testing"
'''
package com.holycityaudio.SpinCAD;
import com.holycityaudio.SpinCAD.SpinCADBlock;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
public class «mn.eResource.className+"Menu"» {
private static final long serialVersionUID = 1L;
public «mn.eResource.className+"Menu"»(final SpinCADFrame f, final SpinCADPanel panel, JMenuBar menuBar) {
«FOR Element m : mn.elements»
«switch m {
MENU_HEADER:{menuNameY = getMenuName(m.name); genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuNameY)}
}»
«ENDFOR»
}
}
'''
}
def String genMenu(MENU_HEADER m) {
'''
JMenu «getMenuName(m.name)» = new JMenu("«m.name»");
menuBar.add(«getMenuName(m.name)»);
'''
}
def genMenuItem(MENU_ITEM m, String menuName) '''
final JMenuItem mntm«m.className» = new JMenuItem("«m.name»");
mntm«m.className».addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SpinCADBlock pcB = new «m.className»CADBlock(50, 100);
f.dropBlock(panel, pcB);
}
});
«menuName».add(mntm«m.className»);
'''
def getMenuName(String header) {
var menuNameX = "mn_" + header.replaceAll("\\s+","")
menuNameX.toLowerCase()
}
}
[Updated on: Fri, 08 November 2013 13:04] by Moderator
|
|
|
Powered by
FUDForum. Page generated in 0.06477 seconds