How to get text representation of AST, or access to TextGen

Hi!

In my language I have a concept that contains baseLanguage.Expression as one of children.
Before my generator is executed, I would like to get a textual representation of the whole AST of the expression, so I could store it as kind of a "command echo", and then - at generation - pass it to my called methods, for debugging and documentation.

As I understand from digging the forum, there was a plan to implement something like this (but with HTML) starting from version 3.0, but I couldn't find a way.

Is there a way to store textual representation of a node's AST (like editor does it on "copy to clipboard"), or to explicitly call TextGen for a node?

Thanks in advance!
0
2 comments
There's "build facets" for this purpose.
https://confluence.jetbrains.com/display/MPSD33/HowTo+--+Integrating+into+the+MPS+Make+Framework

If you get latest sources (or the latest EAP from here https://confluence.jetbrains.com/display/MPS/JetBrains+MPS+EAP+Download+Page), there's a "GenerateImages" facet which does quite a similar thing for documentation purposes (but it outputs an image, and it can use any node produced during generation to output an image).

As for getting the text from an editor, you can look at CellAction_CopyNode in MPS sources, seems to me that
TextRenderUtil.getTextBuilderForSelectedCellsOfEditor(editorComponent)
used there does exactly what you want. This method requires an EditorComponent, which you can obtain like it's done in GenerateImages facet (see PrintNodeRunnable.doPrintNode()).

Regards,
Mihail
0
Hi! Sorry I didn't have time to try it out earlier.

This is is great! I researched TextRenderUtil that you've mentioned and found exactly the solution I need. So now I just get the editor cell for the node I need to render into text, and call EditorCell.renderText on it. Sweeet.

The greater problem was to somehow get access to the EditorComponent each time the required node is updated, and I'm intereseted in any update of its descendants, not only structure. I used UpdaterListener and it works perfect.

So the whole solution looks like this:
1. I have an example concept "echo" that extends Expression with:
    a. Child "value" of type Expression and cardinality [1]
    b. Property "text" of type string

2. Editor for the concept looks kinda like this: echo{ %value% }
    a. Cell %value% has "show if" function with this code:

(editorContext, node)->boolean { 
  node.setEditorContext(editorContext.getEditorComponent()); 
  return true; 
}

3. Concept behavior page looks like this:

public void setEditorContext(EditorComponent editorComponent) { 
  string key = "editor_context_initialized"; 
  Object o = this/.getUserObject(key); 
  if (o instanceof Boolean && ((Boolean) o).booleanValue()) { return; } 
  info "Binding editor for: " + this/.hashCode(); 
  this/.putUserObject(key, true); 
  UpdaterListener listener = createListener(editorComponent); 
  this/.putUserObject("updater_listener", listener); 
  editorComponent.getUpdater().addListener(listener); 
}
          
private UpdaterListener createListener(final EditorComponent editorComponent) { 
  final node<EchoExpression> _this = this; 
  new UpdaterListener() { 
    public void cellSynchronizedWithModel(EditorCell p0) { 
      _this.elementChanged(editorComponent); 
    } 
    public void editorUpdated(EditorComponent p0) { 
      _this.elementChanged(editorComponent); 
    } 
  }; 
   
}        

private void elementChanged(EditorComponent editorComponent) { 
  if (this.parent.isNull) { 
    this.removeListener(editorComponent); 
  } else { 
    this.grabText(editorComponent); 
  } 
}        

private void removeListener(EditorComponent editorComponent) { 
  info "Unbinding editor for: " + this/.hashCode(); 
  string key = "updater_listener"; 
  UpdaterListener listener = ((UpdaterListener) this/.getUserObject(key)); 
  if (this != null) { 
    this/.putUserObject(key, null); 
    editorComponent.getUpdater().removeListener(listener); 
  } 
}        

private void grabText(EditorComponent editorComponent) { 
  EditorCell cell = editorComponent.findNodeCell(this.value); 
  string text = cell.renderText().getText(); 
  string oldText = this.text; 
  if (oldText :eq: text) { return; } 
  try { 
    this.text = text; 
  } catch (Throwable t) { 
    debug String.format("Exception while setting text property from %s to %s", oldText, text), t; 
  } 
}            


Basic logic:
  1. when concept instance is created - "show if" is called
  2. "show if" passes editor component into concept behavior
  3. behavior checks if method was already called and skips if so
  4. If it's the first call - new update listener is created
  5. update listener is stored in node's cache and added to editor component
  6. listener calls the same method on any update passing closured editor component
  7. when event happens parent is checked
    • if parent is null - remove cached listener from editor and ignore event
    • if parent is not null - render text and store it in the "text" property

Notes:

1. Try-catch is required in the "grabText" for property setter may produce exceptions if change happened in the "not undoable context". I don't relly know why but I got such error when expression value was: System.out.println() with no arguments. It is reproducible with 100% probability.

2. Check oldText :eq: text was added when I've noticed that when previously described exception happened it also was the same equal text set to the property. When this ignore block was added - exceptions stopped to happen.

Greatest thanks for the solution!
0

Please sign in to leave a comment.