dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
290 stars 64 forks source link

A simple way to indent multi-line strings #401

Open VBAndCs opened 5 years ago

VBAndCs commented 5 years ago

A simple way to indent multi-line strings I suggest to qute each line, and use no more sympols, like this:

       Dim multiline =
            "Class Test"
            "    Dim x As Integer" 
            "    Dim Y As Integer" 
            "End Class"

Today, this will not compile, so it will not break any thing. All VB should do, is to compile it to:

       Dim multiline =
"Class Test
     Dim x As Integer
     Dim Y As Integer
End Class"

Note: I have another proposal to add indents in the editor itself: https://github.com/dotnet/roslyn/issues/34006 but this one here is just in case the other one didn't happen.

pricerc commented 5 years ago

I can see what you're getting at. Having written a few code generators myself over the years.

VB's multi-line text literals have come a long way since, well, since they didn't exist. But I'm not sure that what you're describing belongs in the compiler.

Large bodies of pre-formated, hard-coded text in the body of a VB program is a 'code smell' to me.

I'm pretty sure strings of any substance are generally considered good candidates for delegating to resource files, template files, or some other data structure.

Your idea also lends itself to the (admittedly edge-case) scenario of accidental edits going unnoticed. e.g. if you have:

       Dim multiline =
            "Class Test"
            "    Dim x As Integer" 
       Dim multiline2 =
            "    Dim Y As Integer" 
            "End Class"

the line Dim multiline2 = could be deleted without being noticed by the editor, where the current mechanism would see it.

VBAndCs commented 5 years ago

@pricerc

the line Dim multiline2 = could be deleted without being noticed by the editor, where the current mechanism would see it.

Good point, but this can happen with different syntax structures, like when you delete an 'else' so its blocks becomes a part of if's block! Such mistakes can happen, and we discover them in runtime, debugging and testing.

pricerc commented 5 years ago

Such mistakes can happen, and we discover them in runtime, debugging and testing.

I did say it was an edge case.

But then VB is also known for things like backward compatibility, and that includes in the editor where this could be seen as a change in behaviour. I'm not judging whether that's good, bad or indifferent, just that it is a change from the current behaviour.

ericmutta commented 5 years ago

Like @pricerc I also understand this problem and so far have been using XML literals to solve most of it:

    Dim multiline2 =
        <x>
          Class Test
            Dim x As Integer
            Dim y as Integer
          End Class
        </x>.Value

...I say "most of it" because the problem with the above is that the indentation in code, also winds up showing in the contents of the <x>...</x> element, so if you print to the console or save to file, you get a lot of leading white spaces. You can fix it by manually removing the indentation in code like this:

    Dim multiline2 =
<x>
Class Test
  Dim x As Integer
  Dim y as Integer
End Class
</x>.Value

...but that just looks plain ugly with the Dim part indented while the <x>...</x> part isn't.

It is a problem for sure, but it is highly unlikely to be solved via a change to the language mainly because it's such a narrow case. However while we are day dreaming, maybe a new operator && could be added so given a && b it generates a & vbCrLf & b. So then you could do this:

    Dim multiline1 =
        "Class Test" &&
        "    Dim x As Integer" &&
        "    Dim Y As Integer" &&
        "End Class"

...and it would produce the correct result as if you had written this more tedious version:

    Dim multiline1 =
        "Class Test" & vbCrLf &
        "    Dim x As Integer" & vbCrLf &
        "    Dim Y As Integer" & vbCrLf &
        "End Class"
VBAndCs commented 5 years ago

I like:

    Dim multiline1 =
        "Class Test" &&
        "    Dim x As Integer" &&
        "    Dim Y As Integer" &&
        "End Class"

but then I will to use implicit && operator so that I get:

    Dim multiline1 =
        "Class Test"
        "    Dim x As Integer"
        "    Dim Y As Integer"
        "End Class"

:)

It is obvious that a sequence of lines of qouted strings mean that they are form a multi-line string. If there is a fear of any issuem we can qute then all in ${}

    Dim multiline1 = ${
        "Class Test"
        "    Dim x As Integer"
        "    Dim Y As Integer"
        "End Class"
    }

Today, we can have this as an array:

        Dim multiline1 = String.Join(vbCrLf, {
                "Class Test",
                "    Dim x As Integer",
                "    Dim Y As Integer",
                 "End Class"
         })
VBAndCs commented 5 years ago

We can also do this but not the best:

        Dim multiline1 =
       "Class Test
" & "    Dim x As Integer
" & "    Dim Y As Integer
" & " End Class"
pricerc commented 5 years ago

I'd be really interested in seeing a more complete example of how y'all are using these big multi-line strings. (I don't think that this Test class is a good example).

While I understand the proposal, I'm also wondering if it's the right tool for the job. As a general rule, I'd regard large blocks of text hard-coded into a string in a code file as a maintenance nightmare.

In my experience, when I have blocks of text to deal with in a code generator, I get the best results when I hard-code as little as possible into the program itself. Ideally, most of it will be in template files (either actual text files, or pulled from a database, resource file or data file, or maybe even an XML "settings" file). Actual building of code, you then do using a string builder initialized using the template.

This is even more true if you're talking about fixed scripts that will be dropped into a web page; in which case they may be better implemented as content files that get deployed with your app, and referenced using an appropriate URL.

VBAndCs commented 5 years ago

I am facing faital editor behavior because of multiline strings as I reported here: https://github.com/dotnet/roslyn/issues/35028 and the new one here: https://github.com/dotnet/roslyn/issues/35028#issuecomment-485071768

This is a practical proof to why VB should get rid of current multiline strings and adapt another representation as the one I suggested here. The worst is that even if I stopped using multi lines and reverted back to classic concatenation, the editor will nor stop confused between code and literals in the whole file if just one quote is missing! I need an option to disable multi lines string literals to feel safe while writing my code. I don't trust the editor any more, and I am doing a lot of work to avoid these mistakes (as partitioning large file and using constants) which kills any benefit from having multilines in first place! I think it is cheaper to change this in the language than in the editor.

VBAndCs commented 5 years ago

@pricerc

I'm pretty sure strings of any substance are generally considered good candidates for delegating to resource files, template files, or some other data structure.

This is a test class I wrote for ZML. Scroll down and see how ugly the text blocks appear out of format. https://github.com/VBAndCs/Vazor-DotNetCore2/blob/master/VazorTest/VazorTest/ZMLTests.vb

It is not practical to move all these chunks to outer resource, because I need to see what I am testing against, and be able to modify any string according to the modification on code.

pricerc commented 5 years ago

@pricerc

I'm pretty sure strings of any substance are generally considered good candidates for delegating to resource files, template files, or some other data structure.

This is a test class I wrote for ZML. Scroll down and see how ugly the text blocks appear out of format. https://github.com/VBAndCs/Vazor-DotNetCore2/blob/master/VazorTest/VazorTest/ZMLTests.vb

You're going to have to be be more specific.

Apart from some heavily indented lines (which I don't understand a need for in XML that you're then parsing), I didn't notice anything particularly 'ugly' about the strings.

It is not practical to move all these chunks to outer resource, because I need to see what I am testing against,...

There's nothing stopping you from seeing what you're testing if you're using 'outer' resources.

Instead of

        <TestMethod>
        Sub TestAllImports()
            Dim x =
$"<z:imports>
...

you can have:

        <TestMethod>
        Sub TestAllImports()
            Dim x = GetResource("TestAllImports", "x")

Where GetResource is a method for getting the relevant resource for your test method. The resource could be in a database, a text file, a .resx file; whatever works for you.

Whatever way x gets populated, it's still just as visible from inside the debugger.

and be able to modify any string according to the modification on code.

And if it's coming from a separate location, external to the executable, then you could be looking at or editing the resource 'source' on one screen while debugging on another, and even change the resource text in between calls to your method, without changing the application source code.

AdamSpeight2008 commented 5 years ago

I am not a member of the Roslyn / VB Language team. As I see it. The contents of a multi-line string literal isn't only restricted to "source code", so the compiler can not automagically handle its indentation. It was noted during its development and was decided not to include as to prevent unexpected surprises. Roslyn/Multi-Line String Literal + Indentation = Gotcha XML has the notion of significant and insignificant whitespace. See: Then there is backwards compatibility to consider. So it looks highly unlikely that handling of indentation, within a string literal, will change soon.

arekbal commented 5 years ago

IMHO we(c# devs) are missing LiteralString and constexpr like thingy. These things would solve these problems - as suggested before - with simple compile time literal string formatting in some 'pure compile time' extension method.

On the other hand I imagine a simpler(arguably better in a sense) lang wouldn't have literals at all (only typed links). Literals of any sort are a heavy burden DSLs in its own sake for the compiler... Buuut if lang is already relying on literals I see no counterpoint for going all the way.

bandleader commented 5 years ago

Iterating on the && concept, you can define a function

Function MultiLine(ParamArray Lines As String()) As String
    Return String.Join(vbCrLf, Lines)
End Function

and then do

    Dim multiline1 = MultiLine(
        "Class Test",
        "    Dim x As Integer",
        "    Dim Y As Integer",
        "End Class"
    )

(It could also be done without the added function, using simply String.Join({ ... })) (I've had this issue as well, but usually just use multi-line strings, and don't care about the wrong indentation level of the first line. To me that is anyway preferable to having to quote each line separately.)

VBAndCs commented 5 years ago

@bandleader I like your String.Join workaround, and luckly, there is one overload that doesn't need an array:.

        Dim multiline1 = String.Join(vbCrLf,
            "Class Test",
            "    Dim x As Integer",
            "    Dim Y As Integer",
            "End Class"
        )