Posted 8 years ago by Stuart Lodge
Avatar
Hi

I'm working with the WPF beta of the .NET add-on and it's great thanks.
The code I'm working on always has the same structure - something like:

namespace Sample 
{
   using System;
   using Cirrious.Engine;

   public class RunEngine : IEngine
   {
      public int Run(EngineParameters e)
      {

         // user enters code here...

      }
   }
}
What I'd like to do is to present the editor so that either:
- the user only sees the "//user enters code here..." block
- or the user sees everything, but can only edit the middle block

Is there any way I can achieve either of these goals with the current Beta?

Thanks for a great product!

Stuart

P.S. I know I've asked this already by email and you suggested: "If you make an IParser class that wraps the real parser on the language and registers itself on the language as the IParser service, you'd first call the wrapped parser and get its return value, then you'd have to wrap the resulting statement list in other AST nodes (like a member, type, and CompilationUnit) that had valid ranges of the entire document range. If you do that, it might allow this to work for now. Let us know how it goes if you try that." - but I've tried that and gotten a bit lost... so any sample/hint would help (hence why I'm asking here on the forum too)

Comments (11)

Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Stuart,

The feature you really want is header/footer text like we have in our WinForms SyntaxEditor, but that isn't in WPF yet.

You might be able to show everything and then lock down ranges of text by handling the SyntaxEditor.DocumentTextChanging event and setting e.Cancel = true if there is an operation that will affect the protected ranges.

The other thing to do is what we had suggested before:

1) Look at the ".NET Languages Add-on - C# Language / Parsing and Parse Data" documentation topic at the Grammar Options section. It talks about how to change the root non-terminal. Make yours a statement list.

2) Make a new IParser class that wraps the core IParser service that is registered on the language. Something like this:
language.RegisterParser(new CustomCSharpParser(language.GetParser());
3) Your CustomCSharpParser class would implement IParser and call all the wrapped parser's methods.

4) However do this in an InitializeParseData override:
protected override void InitializeParseData(LLParseData parseData, IParserState state) {
    base.InitializeParseData(parseData, state);

    // Assume the AST is a BlockStatement via the change in #1 above
    var statementList = parseData.Ast as BlockStatement;
    if (statementList  != null) {
        var cu = new CompilationUnit();
        var ns = new NamespaceDeclaration() { Name = new QualifiedName("Sample") };
        ... and so on until you fill out the AST down to the MethodBody ...
        methodBody.Statements.Add(statementList);

        // Update the AST returned
        parseData.Ast = cu;
    }
}
Note that you MAY need to set the end offset of all the nodes you create to the document's length so that offset-related operations that the resolver performs won't just see a range of 0-0 when it encounters your namespace declaration, etc.

Hope that helps get you going.


Actipro Software Support

Posted 8 years ago by Stuart Lodge
Avatar
Thanks for the suggestions.

I've been playing with this second approach - but haven't quite got there yet...

I've tried to base my "AST" on the output I see in the "Document Outline" window of the samples.

I'm using an inherited Parser rather than a wrapped one - I presume that's not a problem.

Can you see anything obviously wrong with this code below? Using it I don't get any intellisense for parameters or for local variables - but I do get intellisense for "string literals"

        class SpecialParser : CSharpParser
        {
            public SpecialParser(CSharpGrammar g)
                : base(g)
            {                    
            }

            protected override void InitializeParseData(LLParseData parseData, IParserState state) {
                base.InitializeParseData(parseData, state);

                // Assume the AST is a BlockStatement via the change in #1 above
                var statementList = parseData.Ast as BlockStatement;
                if (statementList  != null) {
                    var cu = new CompilationUnit();
                    var uds = new UsingDirectiveSection();
                    cu.Children.Add(uds);
                    uds.Children.Add(new UsingDirective() { Name = new QualifiedName("System", true) });

                    var ns = new NamespaceDeclaration() { Name = new QualifiedName("Fred", true) };
                    ns.Body = new NamespaceBody();
                    cu.Children.Add(ns);

                    var cd = new ClassDeclaration() {Name = new SimpleName("MyClass")};
                    ns.Body.Children.Add(cd);

                    cd.Body = new ClassBody();

                    var md = new MethodDeclaration() {Name = new SimpleName("MyMethod")};
                    cd.Body.Children.Add(md);

                    md.ReturnType = new ReturnTypeDeclaration() {Type = new QualifiedName("System.Void", true)};

                    md.Parameters.Add(new ParameterDeclaration()
                                          {Type = new QualifiedName("System.Int32", true), Name = new SimpleName("p1")});
                    md.Parameters.Add(new ParameterDeclaration()
                                          {Type = new QualifiedName("System.String", true), Name = new SimpleName("p2")});

                    md.Body = new MethodBody();
                    md.Body.Statements.Add(statementList);

                    parseData.Ast = cu;
                }
            }
        }
I'll keep looking at it - but since its almost 2am now.... thanks again for the help and good night!
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Stuart,

Yes that's fine to use an inherited parser, and is probably what I should have written.

It's hard to say without debugging it. Your code looks similar to what I was thinking although as I mentioned at the end, not having the AST nodes you create have valid end offsets (greater than zero) MIGHT cause issues. I can't say for sure if that is the real problem here without debugging though.

If you want to email us a simple sample project after trying the end offset setting, send it to our support address and reference this post. Please rename the .zip file extension so it doesn't get spam blocked. Thanks.


Actipro Software Support

Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Stuart,

I just noticed something... You were adding some AST nodes to the Children property (which is defined on IAstNode), but since the AST model for this add-on uses type-specific nodes and has other properties for AST nodes and AST node collections that should be set instead, updating the Children collection won't do anything since that's dynamically generated on demand.

So for CompilationUnit, add to the Members collection instead. Then do similar for any other places in your code you do Children.Add. See if that helps, and sorry for not spotting that earlier.

Adding to Children would work on any AST node set that isn't code generated, like the ones in our Web Languages Add-on or in the Simple language sample.


Actipro Software Support

Posted 8 years ago by Stuart Lodge
Avatar
That definitely helps a bit

i.e. now I get some code in the AST/document outline.

However, still no luck with intellisense on e.g. parameters.

Here's my latest code:

        class SpecialParser : CSharpParser
        {
            private readonly IEditorDocument _document;

            public SpecialParser(CSharpGrammar g, IEditorDocument document)
                : base(g)
            {
                _document = document;
            }

            private void SetEndOffset(IAstNode node)
            {
                node.EndOffset = _document.CurrentSnapshot.Length;
            }

            protected override void InitializeParseData(LLParseData parseData, IParserState state)
            {
                base.InitializeParseData(parseData, state);

                // Assume the AST is a BlockStatement via the change in #1 above
                var statementList = parseData.Ast as BlockStatement;
                if (statementList != null)
                {
                    var cu = new CompilationUnit();
                    var uds = new UsingDirectiveSection();
                    cu.Members.Add(uds);
                    uds.Directives.Add(new UsingDirective() { Name = new QualifiedName("System", true) });

                    var ns = new NamespaceDeclaration() { Name = new QualifiedName("Fred", true) };
                    ns.Body = new NamespaceBody();
                    cu.Members.Add(ns);

                    var cd = new ClassDeclaration() { Name = new SimpleName("MyClass") };
                    ns.Body.Members.Add(cd);

                    cd.Body = new ClassBody();

                    var md = new MethodDeclaration() { Name = new SimpleName("MyMethod") };
                    md.ReturnType = new ReturnTypeDeclaration() { Type = new QualifiedName("System.Void", true)};

                    md.Parameters.Add(new ParameterDeclaration() { Type = new QualifiedName("System.Int32", true), Name = new SimpleName("p1") });
                    md.Parameters.Add(new ParameterDeclaration() { Type = new QualifiedName("System.String", true), Name = new SimpleName("p2") });

                    md.Body = new MethodBody();
                    md.Body.Statements.Add(statementList);

                    cd.Body.Members.Add(md);

                    // Update the AST returned
                    parseData.Ast = cu;

                    // set the end offsets?
                    SetEndOffset(cu);
                    foreach (var u in cu.Children)
                        SetEndOffset(u);
                    SetEndOffset(uds);
                    foreach (var u in uds.Children)
                        SetEndOffset(u);
                    SetEndOffset(ns);
                    SetEndOffset(ns.Body);
                    SetEndOffset(cd);
                    SetEndOffset(cd.Body);
                    SetEndOffset(md);
                    SetEndOffset(md.ReturnType);
                    foreach (var p in md.Parameters)
                        SetEndOffset(p);
                    SetEndOffset(md.Body);
                }
            }
        }
On a simple statement block like:

                                int i = 12;
                                i++;
                                i--;
This leads to a document outline like:

CompilationUnit[
    UsingDirectiveSection[
        UsingDirective[
            QualifiedName: "System::"
        ]
    ]
    NamespaceDeclaration[
        QualifiedName: "Fred::"
        NamespaceBody[
            ClassDeclaration[
                SimpleName: "MyClass"
                ClassBody[
                    MethodDeclaration[
                        ReturnTypeDeclaration[
                            QualifiedName: "System.Void::"
                        ]
                        SimpleName: "MyMethod"
                        ParameterDeclaration[
                            SimpleName: "p1"
                            QualifiedName: "System.Int32::"
                        ]
                        ParameterDeclaration[
                            SimpleName: "p2"
                            QualifiedName: "System.String::"
                        ]
                        MethodBody[
                            BlockStatement[
                                LocalVariableDeclaration[
                                    VariableDeclarator[
                                        QualifiedName: "global::System.Int32"[
                                            SimpleName: "System"
                                            SimpleName: "Int32"
                                        ]
                                        SimpleName: "i"
                                        LiteralExpression
                                    ]
                                ]
                                ExpressionStatement[
                                    UnaryOperatorExpression[
                                        SimpleName: "i"
                                    ]
                                ]
                                ExpressionStatement[
                                    UnaryOperatorExpression[
                                        SimpleName: "i"
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
]
The QualifiedName's look wrong in this compared to the "normal" samples - is this due to my true/false flags? Changing them doesn't seem to make any difference :/

Still trying to understand this - it's fascinating!

[Modified at 05/25/2011 03:49 PM]
Posted 8 years ago by Stuart Lodge
Avatar
Tried a few more things based around:

                    md.Parameters.Add(new ParameterDeclaration() { Type = CreatedQName("System.Int32", true), Name = new SimpleName("p1") });
                    md.Parameters.Add(new ParameterDeclaration() { Type = CreatedQName("System.String", true), Name = new SimpleName("p2") });
and:

            private QualifiedName CreatedQName(string name, bool isGlobal=false)
            {
                var toReturn = new QualifiedName();
                var split = name.Split('.');
                foreach (var s in split)
                    toReturn.Identifiers.Add(new SimpleName(s));
                toReturn.IsGlobal = isGlobal;
                return toReturn;
            }
This gets me the "right" thing in the document view - but still something is wrong with the "meaning" - still no intellisense.

Will play again tomorrow

G night!
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Just for the sake of anyone reading this, the fix was to set offsets on nodes as well as wire up their Parent node pointers.

The .NET add-on's context factory will use the offsets to recursively find the node that contains the offset that the caret is at. Thus all nodes that are ancestors of the statement list node should be assigned a start offset of 0 and can be left an end offset of null, meaning they are "open" past the end of the document. This won't affect syntax error reporting but will allow the containing node to be properly determined by the context factory. Note that nodes like the using directives should NOT be assigned offsets since those aren't ancestors of the statement list.

Then the resolver takes that containing AST node, which the context factory should determine is the statement list if you did the above correctly, and walks up the AST tree to look for parameters, etc. that have been defined. Normally a last phase of the LL(*) parser is to run through the AST and assign the Parent property of nodes to point to the node that contains them. However since we are manually creating nodes here after that occurs, you must wire up the Parent property of all nodes (through to the statement list one) so that resolver can climb from the statement list node up to the compilation unit.

Doing those things should get it working fine. And hopefully we'll add header/footer abilities in the future so none of this will be needed.


Actipro Software Support

Posted 8 years ago by Jake Pearson - Software Developer, Alion Science and Technology
Avatar
Hi,
I was wondering what the status was on this feature.

thanks,
Jake
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Still on the TODO. Now that we have a good chunk of the desired .NET Languages Add-on features done, we're going to working on new core SyntaxEditor control enhancements for WPF Studio 2011.2. Not sure if this will be part of the first round of those or not yet though.


Actipro Software Support

Posted 8 years ago by Jake Pearson - Software Developer, Alion Science and Technology
Avatar
Thanks for the update.
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Just FYI, as described in this blog post, we've implemented new code fragment editing features for the upcoming 2011.2 version of SyntaxEditor for WPF/Silverlight.
http://blog.actiprosoftware.com/post/2011/08/18/SyntaxEditor-Code-Fragments.aspx


Actipro Software Support

The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.