compiler icon indicating copy to clipboard operation
compiler copied to clipboard

Multi-line strings

Open thecodeah opened this issue 6 years ago • 26 comments

Is this a BUG REPORT, FEATURE REQUEST or QUESTION?:

  • [ ] Bug Report
  • [x] Feature Request
  • [ ] Question

The issue name is kind of self-explanatory, I suggest adding multi-line strings. These could be useful in multiple situations, I've listed a few of them myself down below in the Usages section.

Syntax

I suggest using the same syntax Go uses for multi-line strings, Go uses back-ticks. Alternatively, triple double quotes are also an option """string content""" (Used by Python), or maybe even just single quotes 'string content'.

However, personally I think the Go syntax would be best.

Usages

SQL Queries :

new Cache:cache = mysql_query(MySQL,
    `SELECT
        groups.name,
        groups.points
    FROM
        groups INNER JOIN user_groups ON groups.id = user_groups.group_id
    WHERE user_groups.user_id = 1;`
);

Dialogs (Although in this example, it doesn't make too much of a difference) :

ShowPlayerDialog(playerid, dialogid, style, caption[],
    `Dialog Line #1\n
     Dialog Line #2\n
     Dialog Line #3\n
     Dialog Line #4\n
     Dialog Line #5\n
     Dialog Line #6\n`,
button1[], button2[]);

Feel free to add your own usages down below as I'm sure there are a lot more.

thecodeah avatar Jun 29 '18 13:06 thecodeah

I'd love to see this too, multi-line strings can get messy with C-style literals due to the excessive use of backslashes to indicate newlines.

One question that always comes up is how to handle the excess whitespace. We already have a bug in 3.10.7 that causes excess whitespace to be included in the resultant string. Some languages actually do this by design but I've always found that to be really annoying and ugly to read:

        {
            SomeFunctionCall(`This is the first line
this is the second line
see how it *must* return to the gutter?
very ugly and breaks the entire visual scope structure
`,
                other,
                arguments,
                to_the,
                function);
        }

I've always preferred the solution of ignoring whitespace up to the same column as the start of the string, so:

        {
            SomeFunctionCall(`This is the first line
                              this is the second line
                              see how it *must* return to the gutter?
                              very ugly and breaks the entire visual scope structure
                              `,
                other,
                arguments,
                to_the,
                function);
        }

Or

        {
            SomeFunctionCall(
                `This is the first line
                this is the second line
                see how it *must* return to the gutter?
                very ugly and breaks the entire visual scope structure
                `,
                other,
                arguments,
                to_the,
                function);
        }

both ignore the 30 or 16 (respectively) columns that are present before each line of the string.

Southclaws avatar Jun 29 '18 13:06 Southclaws

If it were included, it should include spaces - if you want them excluded there's already a way to do that (bug not withstanding). Two different options shouldn't do the same thing or what is the point?

Y-Less avatar Jun 29 '18 18:06 Y-Less

Oh do you mean concatenation?

    func(
        "line1" \
        "line2"
    );

Yeah that makes sense, but I think if a multi-line string syntax such as backticks or triple quotes were added, it would be nice to make use of column indentation without it going into the string literal.

Southclaws avatar Jun 29 '18 19:06 Southclaws

What about an approach similar to this if somehow possible?

Just throwing it in here: https://repl.it/repls/LovingBigAdvance

Bios-Marcel avatar Jun 29 '18 19:06 Bios-Marcel

I just realised another brilliant benefit of Go style backtick strings. They aren't just multi-line strings, they are fully escaped strings, which means you can use \ and " literals without the compiler attempting to do anything special with those characters.

Very useful for cases like:

new input[] = "{\"list\":[{\"a_listobj_float\":66.599998474121094,\"a_listobj_number\":76,\"a_listobj_string\":\"another value\",\"one\":\"value one\"},{\"a_listobj_float\":66.599998474121094,\"a_listobj_number\":76,\"a_listobj_string\":\"another value\",\"two\":\"value two\"},{\"a_listobj_float\":66.599998474121094,\"a_listobj_number\":76,\"a_listobj_string\":\"another value\",\"three\":\"value three\"}],\"object\":{\"a_float\":66.599998474121094,\"a_number\":76,\"a_string\":\"a value\",\"nested_object\":{\"a_deeper_float\":66.599998474121094,\"a_deeper_number\":76,\"a_deeper_string\":\"another value\"}}}";

Which is a unit test string for the Requests plugin JSON encoder. Pretty ugly!

Southclaws avatar Jul 02 '18 12:07 Southclaws

Personally I'd prefer markdown-style strings, i.e.

```
hello
```

(which is non-trivial to display in markdown btw).

Y-Less avatar Jul 02 '18 12:07 Y-Less

A problem with """ is that it is technically already valid syntax thanks to implicit concatenation: """hello""" already works (empty string "" followed by "hello" followed by "" again, all joined together).

Y-Less avatar Jul 02 '18 12:07 Y-Less

What is wrong with:

"abc" \
"asdasdasdas"\
"adasdasd"

and I wonder why this doesn't work:

"abc" 
"asdasdasdas"
"adasdasd"

when this works

"abc"       "asdasdasdas"                "adasdasd"

YashasSamaga avatar Jul 02 '18 14:07 YashasSamaga

@YashasSamaga The main reason I suggested this was due to readability issues with big SQL queries.

new Cache:cache = mysql_query(MySQL,
    "SELECT " \
        "groups.name, " \
        "groups.points " \
    "FROM " \
        "groups INNER JOIN user_groups ON groups.id = user_groups.group_id " \
    "WHERE user_groups.user_id = 1;"
);

This is what it'd look like if you wrote a big SQL query in that way. Not only is it harder to read, but it's also a lot more annoying to write. Versus how it would look like if multi-line strings were to be implemented. (This time using the syntax Y_Less suggested)

new Cache:cache = mysql_query(MySQL,
    ```
    SELECT
        groups.name,
        groups.points
    FROM
        groups INNER JOIN user_groups ON groups.id = user_groups.group_id
    WHERE user_groups.user_id = 1;
    ```
);

thecodeah avatar Jul 02 '18 15:07 thecodeah

Using muliple lines of "..." \ is functionally fine but the issue is, the additional " \ characters just add unnecessary clutter to parse while reading.

And I agree with Y_Less regarding using ``` instead of """.

Southclaws avatar Jul 02 '18 15:07 Southclaws

What about:

new const str[] = "This is a multi line string"                 \
                  "which contains lines of variable sizes"      \
                  "but can still be made to look good"          \
                  "with some nice indentation";

That might need some effort to update the string as it could screw up the indentation keeping \ in their column.

What if this was allowed:

new const str[] = "This is a multi line string"                 
                  "which contains lines of variable sizes"      
                  "but can still be made to look good"          
                  "with some nice indentation";

YashasSamaga avatar Jul 24 '18 14:07 YashasSamaga

Those who wish to see this in the compiler, add thumbs up to the issue and those who are against it add thumbs down. If there is enough consensus, we could decide about the syntax.

YashasSamaga avatar Jul 24 '18 14:07 YashasSamaga

Custom syntax would only lead to more confusion IMO, so it should be avoided where possible. Single quotes probably wouldn't even work since these are used to denote character constants.

There should be options to read large strings from files, but I think that's hardly compiler related.

Vince0789 avatar Jul 24 '18 14:07 Vince0789

@YashasSamaga

new const str[] = "SELECT "                 
                  "groups.name,"
                  "groups.points "          
                  "FROM "
                  "groups INNER JOIN user_groups ON groups.id = user_groups.group_id "
                  "WHERE user_groups.user_id = 1;";

Notice how this once again becomes a mess once you try using it with SQL queries. You have to constantly add quotation marks and spaces at the end of each line.

@Vince0789 What confusion is there in the first place, and how would this cause even more confusion?

thecodeah avatar Jul 24 '18 14:07 thecodeah

@thecodeah Generally, we have to be conservative while introducing new features because reverting them back in the future is not feasible as this would break existing codebases. A new feature should be added if and only if it is truly necessary.

YashasSamaga avatar Jul 24 '18 14:07 YashasSamaga

There's quite a difference between "confusion" and simply learning a new feature.

Southclaws avatar Jul 24 '18 14:07 Southclaws

Whatever is done, spaces should absolutely be explicit, so this:

new const str[] = "This is a multi line string"                 
                  "which contains lines of variable sizes"      
                  "but can still be made to look good"          
                  "with some nice indentation";

Would give:

new const str[] = "This is a multi line stringwhich contains lines of variable sizesbut can still be made to look goodwith some nice indentation";

"stringwhich" (is that like a sandwich?), "sizesbut", "goodwith".

Y-Less avatar Jul 24 '18 14:07 Y-Less

In retrospect, Just ` (one backtick) would IMHO be better than three. This is what JS does, and it becomes vastly simpler to type in examples.

Y-Less avatar Sep 29 '20 17:09 Y-Less

So, question, why do we need custom syntax at all? @Vince0789 briefly touched on this, but I didn't really notice at the time (sorry) because other languages use different syntax for multiline strings, so so should we. Why? Is there any good reason not to make normal " strings able to span multiple lines? I implemented `, but then it broke YSI. I know that's not a good reason not to update the compiler, and I do keep on top of keeping YSI updated with compiler changes, but it did get me thinking about the syntax more (because ` is quite deeply ingrained in a lot of YSI).

Y-Less avatar Oct 13 '20 12:10 Y-Less

Y'know what? I never thought of that...

I don't see any reason not to make " just support newlines.

Southclaws avatar Oct 13 '20 13:10 Southclaws

Neither had I. Just seemed like the way to do it, but then I just realised while writing my latest PR reply, literally mid post.

Y-Less avatar Oct 13 '20 13:10 Y-Less

How should indent and non-graphical characters that stray in be handled? It probably wouldn't matter for SQL queries (although it might make the logs look ugly).

YashasSamaga avatar Oct 13 '20 13:10 YashasSamaga

For example?

Y-Less avatar Oct 13 '20 13:10 Y-Less

new str[] = "ABCDEFG
                    123456778
                     aabcdefgh";

If I have understood correctly, this would effectively unroll to:

"ABCDEFG\r\n                    123456778\r\n                     aabcdefgh\0"

new const str[] = "SELECT            
                  groups.name, 
                  groups.points          
                  FROM 
                  groups INNER JOIN user_groups ON groups.id = user_groups.group_id 
                  WHERE user_groups.user_id = 1; ";

would become

SELECT\r\n                  groups.name, \r\n                  groups.points \r\n                  FROM \r\n                  groups INNER JOIN user_groups ON groups.id = user_groups.group_id\r\n                   WHERE user_groups.user_id = 1;

Even in the printed form, there will be indentation for all the lines except SELECT.

YashasSamaga avatar Oct 13 '20 13:10 YashasSamaga

Yes, that's literally the point of these strings.

Y-Less avatar Oct 13 '20 13:10 Y-Less

Also, I just discovered that the \0 isn't actually stored with the string in the AMX, just implicitly added when moving the data in to a local. Not sure about function parameters.

Y-Less avatar Oct 13 '20 13:10 Y-Less