Xtend local variable scope? [message #1171811] |
Tue, 05 November 2013 13:56 |
Gary Worsham Messages: 176 Registered: September 2013 |
Senior Member |
|
|
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 14:16] Report message to a moderator
|
|
|
|
|
|
Re: Xtend local variable scope? [message #1173364 is a reply to message #1172910] |
Wed, 06 November 2013 13:19 |
Gary Worsham Messages: 176 Registered: September 2013 |
Senior Member |
|
|
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 14:46] Report message to a moderator
|
|
|
|
Re: Xtend local variable scope? [message #1173883 is a reply to message #1173462] |
Wed, 06 November 2013 20:30 |
|
Hi,
maybe this one helps
the code
def String genMenu(MENU_HEADER m) {
var menuNameX = "mn_" + m.name.replaceAll("\\s+","")
'''
JMenu «menuNameX» = new JMenu("«m.name»");
menuBar.add(«menuNameX»);
'''
menuNameX.toLowerCase()
}
translates to Java (Pseudo)Code
public String genMenu(MENU_HEADER m) {
String menuNameX = "mn_" + m.getName().replaceAll("\\s+","");
StringBuilder b = new StringBuilder();
b.append("....");
b.append(m.getName());
b.append("...");
return menuNameX.toLowerCase()
}
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
|
Re: Xtend local variable scope? [message #1173939 is a reply to message #1173901] |
Wed, 06 November 2013 21:16 |
|
Hi,
you add stuff to the buffer but you do not anything with the buffer. xtend wants to prevent you from doing such things.
the last expression of a block is its return value
=> you return menuNameX.toLowerCase()
you call getmenu here
«switch m {
MENU_HEADER:{menuName = genMenu(m)}
MENU_ITEM:{genMenuItem(m, menuName)}
}»
the switch expression itself returns a string and this is embedded into the rich string ''''''
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
|
|
|
Re: Xtend local variable scope? [message #1175084 is a reply to message #1174190] |
Thu, 07 November 2013 14:14 |
Gary Worsham Messages: 176 Registered: September 2013 |
Senior Member |
|
|
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 14:15] Report message to a moderator
|
|
|
|
Re: Xtend local variable scope when using template code? [message #1176716 is a reply to message #1176194] |
Fri, 08 November 2013 14:06 |
Gary Worsham Messages: 176 Registered: September 2013 |
Senior Member |
|
|
Thanks for the response Uli.
I still have the problem about trying to create a variable in my method that can both be used in the template section as well as be a return value.
For example, this is fine, but doesn't emit any template code:
def figureItOut2(MENU_HEADER m) {
var menuNameX = getMenuName(m.name)
menuNameX
}
This is fine, but doesn't return any useful value:
def figureItOut(MENU_HEADER m) {
var menuNameX = getMenuName(m.name)
'''
JMenu «menuNameX» = new JMenu("«m.name»");
menuBar.add(«menuNameX»);
'''
}
This is NOT fine (it's what I originally tried):
def figureItOut(MENU_HEADER m) {
var menuNameX = getMenuName(m.name)
'''
JMenu «menuNameX» = new JMenu("«m.name»");
menuBar.add(«menuNameX»);
'''
menuNameX
}
And this is NOT fine - attempting to use the menuNameX value past the end of the template section (it was declared within the template section):
def figureItOut1(MENU_HEADER m) {
'''
JMenu «var menuNameX=getMenuName(m.name)» = new JMenu("«m.name»");
menuBar.add(«menuNameX»);
'''
menuNameX
}
Error: The method or field menuNameX is undefined for the type SpinCADMenuGenerator
So it looks to me as though the template section represents a scope boundary. There's no way to insert a template section in the middle of some code without breaking the scope in half.
For reference, the getMenuName() method is:
def getMenuName(String header) {
var menuNameX = "mn_" + header.replaceAll("\\s+","")
menuNameX.toLowerCase()
}
[Updated on: Fri, 08 November 2013 14:12] Report message to a moderator
|
|
|
Re: Xtend local variable scope? [message #1176760 is a reply to message #1171811] |
Fri, 08 November 2013 14:39 |
Gary Worsham Messages: 176 Registered: September 2013 |
Senior Member |
|
|
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 18:04] Report message to a moderator
|
|
|
Powered by
FUDForum. Page generated in 0.03690 seconds