Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Interpolated String Nesting #273

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

fix: Interpolated String Nesting #273

wants to merge 1 commit into from

Conversation

numv
Copy link

@numv numv commented May 9, 2019

Regarding Issue 263
Could you please check.

Your current interpolated nesting check is losing all its previous progress to the state.
I mean i you end an nested interpolated string you think that you are inside an normal Text again but you are still inside the parent interpolated string.

I have no idea what happens on your Reset method so i left the original variables.
Probably should implement a check in the nestingIndex setter so that there is always the default NestingOption.

@tomasr
Copy link
Owner

tomasr commented Jul 4, 2019

Sorry for taking so long to look at this. I appreciate the pull request!

However, I'm not sure what some of the changes you did actually do....

There are a couple of things that would need to be fixed:

  1. Please do this against the develop branch; I never commit directly to master.
  2. The Reset() part is important, and will likely affect your code. Some explanation below:

What happens is that when we're processing the text buffer to identify braces, the scanner starts looking just within a portion of the file. Viasfora doesn't parse the entire buffer in one go, usually, but rather some part of it that is visible (plus some additional text to allow for faster scrolling).

When you scroll, then, we'll restart scanning, but not from the top of the file, but from the last brace found. Before doing so, the scanner needs to be "reset" to avoid maintaining any state when the scanning is restarted. This is because we might also be restarting because the buffer was modified, and so any state in the scanner object could cause wrong behavior.

That also means that any state that the scanner stores needs to be restartable. There are a couple of ways this is done:

  • When a new CharPos value is returned, we encode the current scanner state into the value. That's why currently, the only state the scanner maintains needs to be encoded into a single int value.

  • Before the scanner is restarted, CanResume() is called. This allows the scanner to tell Viasfora that the brace we're restarting from is a "safe place" to restart (meaning, one from which the scanner can recover either the scanner state, or one from which it would not need any state to start from).

So that means that in your code:

  • The complex nesting state in the scanner cannot be encoded in CharPos.
  • It can only restart from a brace that is not nested within an interpolated string at all, since there's no way to persist the nesting state due to the issue above.

Does this clarify things somewhat?

@numv
Copy link
Author

numv commented Jul 4, 2019

Thanks for taking your time trying to explain.

Did i understand correctly that the the CSharpBraceScanner stops even if CanResume() is false?
Can we then simply modify the code that we try keep scanning until CanResume() is true?
So when we are in a Interpolated Nested String that is not in the visible text area we keep looking ahead for the end of the nested string.
So that the NestingList keeps the Information of previous nestings so you know when you left all nestings.

I am guessing it will be a performance problem if you haven't yet typed the end of the nested string so the scanner won't stop in a large file. I mean i could live with that.
Maybe with adding a settings in Visual Studio, where you can input a number of characters the Scanner shall continue to look forward if CanResume() is false and you are stuck in a Interpolated String Nesting.
If the Value is exceeded just behave like normal and show an error with the braces.

@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

It's not that the scanner stops on its own. It's that the text buffer parser will simply stop feeding it content at any arbitrary time, based on the buffer region it decides to parse.

What's important is how it restarts parsing.

Let's say the previous parse operation stopped at character 1035, and it needs to restart. It will then start backtracking to braces found before position 1035, and for each one it will call CanResume(pos). If it returns false, it will keep backtracking to the previous one. If it returns true, it will then call Reset(pos) and start parsing again.

Does that make sense?

So since you can't keep the complex state you are creating, then you want to make sure CanResume() only returns true if the position is not within an interpolated string (i.e. the nesting stack is empty, I would guess)

@numv
Copy link
Author

numv commented Jul 5, 2019

Ah i see okay.
Will it resume with the same instance of the CSharpBraceScanner or will a new Instance be created, when the parsing restarts?

@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

You should not care, since you can't keep any state at all after Reset() is called.

@numv
Copy link
Author

numv commented Jul 5, 2019

Mh but i thought there is the brace.state in CanResume?

So if i tell the Barce it is an nestedText instead of stText like it is right now because nesting currently wont get parsed it will not resume at that position.

I'm gonna make an example hold on :)

@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

Yes, that is used to reconstruct your state. But that's only an int. You can't put a vector in there in any way.

The biggest issue here isn't restarting where you left off. It's restarting after the buffer contents have changed.

Let me give you an example.

Let's say the last parse operation parsed until buffer position 1032. That's in the middle of an interpolated string, so you have nesting state in your scanner instance.

Then the buffer is changed at position 974, so Viasfora will try to restart from position 952, which is before the interpolated string opens. Then all the nesting state in your current scanner instance is completely invalid. You can't use it at all because it doesn't reflect the actual buffer state from the position you will restart.

Does that make sense?

@numv
Copy link
Author

numv commented Jul 5, 2019

  • 2000 lines of code
    • line 950 start of nested interploated string
    • line 1050 end of nested interpolated string
  • buffer pos 1032
    • inside the nested interpolated string.
  • buffer goes to position 974;
    • CanResume() going backwards through the braces
    • braces.state is not stText because it is inside interpolated string
  • buffer goes to position 964;
    • still inside interpolated String CanResume() goes further back
  • buffer at position 950
    • left interpolated string
    • CanResume() == true so parsing will start from 950
    • Reset() called. (lost nesting progress)
  • viasfora continues and parses from 950 before the interpolated string

Sorry I can not understand the problem. 😟
The Scanner does not need to save the complex state.
The braces inside the nested Interpolated String only need to know they are not stText.
The current code when it sees code like this:

string test = "";
List<string> testList = new List<string>();
test = string.IsNullOrEmpty($"{test} {(string.IsNullOrEmpty(test) ? "" : testList.Select(n => $"pre_{n}_suf").Aggregate((n, m) => n + ", " + m))}");

the code stops parsing the interpolated string as an interpolated string after the round ) in => $\"pre_{n}_suf\").Aggregate and gives the brace.State as stText because the nesting is not handled.

My modification handled the nesting so stText will not be given back.
I don't need to remember the current nesting state because it will just restart before the Interpolated String.

Or am i missing something?

var indx = this.nestingIndex;
if(indx -1 < 0) {
this.multiLine = false;
this.nestingIndex = 0;
this.status = stText;
Copy link
Author

@numv numv Jul 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This in combination with the start in line 177.
Nesting got lost and stText was returned but it was still inside the nested string

@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

I'm not sure I fully follow here, but yes, the biggest danger is going to be when the text buffer contents change.

@numv
Copy link
Author

numv commented Jul 5, 2019

I'm gonna try saying it a bit differently. If we not look at the nested interpolated strings, your current code with normal interpolated strings will start with parsing the braces at the start of the interpolated string right? because the CanResume functions only returns true if the brace. State

@numv numv closed this Jul 5, 2019
@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

If I'm understanding this right, then the answer would be yes, that's correct.

Sorry if I wasn't getting what you were driving at.

@numv
Copy link
Author

numv commented Jul 5, 2019

oh i am sitting in a car and a bump on the street accedently made me close the ticket.

i wanted to say the CanResume will backpadel until brace.State is stText so before the Interpolated string did start right?

@numv numv reopened this Jul 5, 2019
@tomasr
Copy link
Owner

tomasr commented Jul 5, 2019

stText in itself isn't important, but I get what you mean. What matters is that CanResume() never returns true for a brace that is nested in an interpolated string.

That will force the buffer processor to backtrack until the last brace before the interpolated string beginning when restarting.

@numv
Copy link
Author

numv commented Jul 5, 2019

Your current code doesnt backtrack because it has a problem in detecting the nesting level.
If you take my testcase in the PR CSharpBraceScannerTests.cs and debug your code without the other changes you will find that every brace after the _{n}_suf\").Aggregate will be returnd as stText.
So to fix the problem mentiond in #263 i tried to keep better track of the nesting which i achived with the List<NestingOption> nestings

@numv
Copy link
Author

numv commented Jul 5, 2019

Looking at

 public void Bug263_InterpolatedStringWithInterpolatedString1() {
      // bug is the missing ) in following section of the teststring:
      // n => $\"pre_{n}_suf\").Aggregate

      String input = "string.IsNullOrEmpty($\"{test} {(string.IsNullOrEmpty(test) ? \"\" : testList.Select(n => $\"pre_{n}_suf\").Aggregate((n,m) =>  n + \", \" + m))}\")";
      var extractor = new CSharpBraceScanner();
      var chars = Extract(extractor, input.Trim(), 0, 0);
      Assert.Equal(1 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 3 + 1, chars.Count);
    }

Before:

Debug Output:
CharPos 93 where it fails is the {n} inside the nested interpolated string.
It is parsed as a Text but that let's you forget that you are inside a interpolated string.

TEXT
ParseText ** Char: 40 '(', CharPos: 20, CharState: 0, CurrState:  0
ParseText ** Char: 123 '{', CharPos: 23, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 125 '}', CharPos: 28, CharState: 5, CurrState:  5
ParseInterpolatedString ** Char: 123 '{', CharPos: 30, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 31, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 52, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 57, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 81, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 123 '{', CharPos: 93, CharState: 151060485, CurrState:  5
ParseInterpolatedString ** Char: 125 '}', CharPos: 95, CharState: 65541, CurrState:  5
TEXT
ParseText ** Char: 41 ')', CharPos: 101, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 40 '(', CharPos: 112, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 40 '(', CharPos: 113, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 41 ')', CharPos: 117, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 41 ')', CharPos: 135, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 41 ')', CharPos: 136, CharState: 0, CurrState:  0
TEXT
ParseText ** Char: 125 '}', CharPos: 137, CharState: 0, CurrState:  0
Extract FALSE, Char: 125 '}', CharPos: 137, CharState: 0, CurrState:  0

Resulting in:
image

After:

Debug Output:

TEXT
ParseText ** Char: 40 '(', CharPos: 20, CharState: 0, CurrState:  0
ParseText ** Char: 123 '{', CharPos: 23, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 125 '}', CharPos: 28, CharState: 5, CurrState:  5
ParseInterpolatedString ** Char: 123 '{', CharPos: 30, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 31, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 52, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 57, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 81, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 123 '{', CharPos: 93, CharState: 151060485, CurrState:  5
ParseInterpolatedString ** Char: 125 '}', CharPos: 95, CharState: 65541, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 101, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 112, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 40 '(', CharPos: 113, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 117, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 135, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 41 ')', CharPos: 136, CharState: 150994949, CurrState:  5
ParseInterpolatedString ** Char: 125 '}', CharPos: 137, CharState: 5, CurrState:  5
TEXT
ParseText ** Char: 41 ')', CharPos: 139, CharState: 0, CurrState:  0

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants