GH/C# - Text dissappearing when generating code with \n nad \t and... so on

Yeah, so I’m generating some code with a script component, and I output the code into a text panel,. However, when saving the text to disk and opening it in a text editor or VS, part of the text (strings) has disappeared. For example ;

var template = "\n\tfor (i=0;i<10;i++)\n";

You get the idea. The “\n” and “\t” part won’t survive going through the Panel and streamed down to disk. Which means that my little code generator doesn’t really work as expected.

Q1: So how can I arm such code (strings) to survive the export to a file on disk?

Fixed: This was due to using wrong variable. Q1 remains an issue though.
Q2: Moreover, the script code part being exported is “clipped”, so that the main method of the scirpt component isn’t exported in full (see after the highlighted " char). Is there a limit to the size with which the getter utility can retrive data from the component, or is it the panel which is pulling my leg? (see code snippet below the picture).

    // <main script>
    var scriptcode = "";
    var source = Grasshopper.Utility.InvokeGetterSafe(_SourceComponent, "ScriptSource");
    var _obj = Grasshopper.Utility.InvokeGetterSafe(source, "ScriptCode");
    if (_obj != null)
      scriptcode = _obj.ToString();

The variable “scriptcode” (code above) contains truncated code (only 360 out of 374 code rows) from the main method (RunScript) is exported, so the exported code text isn’t complete.

Are the two problems related?

Any hints on what can be the probelm or what to try as workaround?

// Rolf

Are you sure the newlines and tabs are not surviving the save? If you get newline and indentation it works just fine. Open up in an editor that can visualise whitespace.

How are you saving the text to disk?

Just FYI, in v6 I use Grasshopper to generate C# code for my material conversion from Rhino to Cycles. I copy/paste from the panel, but that all otherwise seems to be working just fine…

/Nathan

Well, that depends on what we mean with survive. An example of the result after export can be seen on the line in the picture which has the text that needs to be armored, and how it has:

if (added_code.Length > 0) { added_code = added_code.Replace("\n", "\n\t"); }

this line, containing the \n and a \t\t really works, since it has filled the line with whitespace, like so:

if (added_code.Length > 0) { added_code = added_code.Replace("...............", "........................."); }

So question is, how do I armor the “Replace("\n", "\n\t")”-part so it survives into the target file unaffected?

The problem might not be obvious in this case because the code that you see in the picture is itself the code that was produced from a copy of itself, but the code of this copy will not be able to do the same thing another time, since the “Replace("\n", "\n\t")”-part is no longer intact. If you see what I mean.

A screenshot shows how my codegen component analyses a copy of itself (on top) via a wire, and then generates code which is meant to be pasted into VS to make a compiled version of the component. (it could of course have analysed any ScriptComponent, but in this case it copies itself):

Fig. Here the codegen “reads” the other component via any outport on the other component ("Warnings" in this case) which is connected to the SourceComponent inport:

Everything works just perfect (a Visual Studio-code version of the component produced in 6 ms) except for the String.Format-part that needs to be armored. Hm, perhaps I could use the ASCII codes instead… ?

// Rolf

I guess before going into the string manipulations we should first learn how you are writing the code to disk. Are you using TextWriter or some similar class?

I don’t see why writing a file with formatted strings containing newlines and tabs would lose those. The file writing classes in .NET aren’t lossy like that.

For now I saved it via the “Stream Contents” option on the Panel component.

// Rolf

I don’t know what stream contents does internally, but in your own code make sure you are not using .Trim() on your strings after formatting.

@DavidRutten will know better what goes on with streaming contents.

Yup, no such thing is involved.

One could question why using “Stream Contents” for something like this. The reason is that I can have the resulting file, with the generated code, open in VS and try if it can compile after every little modification to the source component (sometimes the compiler won’t be happy with the port names, or the types are messed up between the registered ports and the actual types used in the code, or in the properties or… well, it can become messy since the ScriptComponents tends to mess up the ports names internally, and no type is given to the outports, except for System.Object so one would have to do some guesswork there).

Let’s see what @DavidRutten has to say about this.

// Rolf

You can always write the code to disk using TextWriter or some similar useful .NET class. Then at least you know what happens with the data you create.

You should not use \n, as that only works on unix. \r only works on MacOs, windows uses \r\n. You are better of using Environment.NewLine, which always returns the correct character sequences based on the current platform.

Panels may well strip whitespace characters such as tabs. There’s little you can do about that. Basically, panels will mangle whatever data you put in them in order to make it more displayable.

If you really need to write text with full control over every whitespace character (and possibly the encoding), then I recommend writing it yourself. My preferred method is to compose the string in memory using a System.Text.StringBuilder, or otherwise a collection of strings (if your text is composed line by line this may make more sense), and then use System.IO.File.WriteAllText to dump it into a file. Try to stay away from streams and writers and such unless you have no choice any more. You’ll need memory or file streams if (a) you do not know when you will be done writing (b) are writing too much data to keep it all in memory or (c) want to be able to retrieve some of what you’ve written in the event of an application crash.

I want to see the generated code as I type in the component being code-generated. So I’ll keep the Panel-solution for now.

Armored symbols
I just tested a a solution in which I “armor” formatting symbols in three levels, with a textual representation (N_0 = ascii (09)) and N_1 = “\n” and N_2 = “{{N_2}}”. The different “levels” to be used in code templates, in code strings or for directly formatting the generated code ( = 3 levels). Tabs following the same pattern (T_0, T_1, T_2).

It all works fine now.

Not all type conversions are yet resolved though, although I cover the most frequently used ones. But the GH_Goo types for DataTrees is a bit of black magic since I have not used them (data trees) very much as of yet. Does it exist any example code for converting value types to their DataTree<GH_Goo> equivalents?

It would also be nice to be able to set a Type for the outports, which the CodeGen could pick up as to make DA.SetData(...,...)-setters (property setters) with the correct data type for the target code.

Edit: Regarding “generating line by line”, no, I generate chunks like complete properties, class members, but yes, RegisterInportParams is a line-by-line list construct which is expanded into a place holder in the class template, but chunks (smaller templates expanded into the global class template) is the most common method.

// Rolf

Example: A property template which I expand separately for each portname, and then add to a List<string>, and finally I inject the list into a placeholder in the global template named something like “#(PROPERTY_GETTERS)#” etc. Templates are feed into the Codegen component via inports ClassTemplate and PropertyTemplate for override of the hard coded versions inside.

\n
\t\tprivate #(PROPERTY_TYPE)# #(PROPERTY_NAME)#
\t\t{
\t\t\tget {
\t\t\t\t#(M_PROPERTY_SETINITVALUE)#;
\t\t\t\tif (!m_DA.#(GETDATA)#(IN_#(PROPERTY_NICKNAME)#, #(ITEM_REF)# m_#(M_PROPERTY_NAME)#))
\t\t\t\t\tthrow new System.ArgumentNullException("Invalid in-parameter '#(PROPERTY_NAME)#' (#(PROPERTY_NICKNAME)#).");
\t\t\t\treturn m_#(M_PROPERTY_NAME)#;
\t\t\t}
\t\t}

The “global” class template starts like this, and so on:

/*#(CODEGEN_INFO)#
*/
#(WARNINGS)#
#(DESCRIPTION)#
#(COPYRIGHT_INFO)#
using System;
using System.Collections.Generic;
#(USING)#
using Grasshopper.Kernel;
using Rhino.Geometry;

/*
#(n)##(AUTHOR_INFO)#
#(n)##(SOURCECOMPONENT_INFO)#
*/

namespace #(NAMESPACE)#
{
    public class #(CLASSNAME)#: #(SUPERCLASSNAME)#
    {
        private const string VERSIONINFO = "#(VERSION_INFO)#";

        public #(CLASSNAME)#()
          : base("#(CLASSNAME)#", "#(NICKNAME)#",
               "#(CLASS_DESCRIPTION)#\n"
             + "----------------------\n"
             + VERSIONINFO,
              nameof(#(CATEGORY)#), "#(SUB_CATEGORY)#")
        {#(KEYWORDS)##(MESSAGE)#
        }
...

and… blah blah. I also have methods for IncArmorLevel and DecArmorLevel to handle any situation where I need to manipulate text which has the “wrong level” causing trouble.

So now it all works.

// Rolf