Skip to content

Inline parsing overhaul WIP - #180

Merged
robinst merged 15 commits into
masterfrom
inline-parsing
Sep 8, 2020
Merged

robinst merged 15 commits into
masterfrom
inline-parsing

Conversation

@robinst

@robinst robinst commented Jul 27, 2020 •

Copy link
Copy Markdown
Collaborator

The end goal is to extract all current inline parsers, and then allow customizing inline parsing via the same infrastructure (similar to the current block parsing).

A stretch goal is to move away from the parser input having to be a single contiguous String. This could speed up parsing and allow keeping track of source positions (line and column indexes) for inline nodes.

Still todo:

  • Replace remaining direct usages of index and match in InlineParserImpl with Scanner
  • See if we can change input from String to List<CharSequence>
  • Clean up internal API

(Allowing extension of inline parsers can be done in a separate PR, but it will be much simpler now.)

import org.commonmark.internal.util.CharMatcher;
import org.commonmark.internal.util.Parsing;

public class Scanner {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This scanner class helped a lot the code readability. :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I think so too :).

return '\n';
} else {
// Don't return newline for end of last line
return '\0';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I can see we using this symbol in multiple places. What do you think to extract and also use the constant from Character class? Character.MIN_VALUE?

@robinst robinst Aug 3, 2020 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, yeah! Character.MIN_VALUE would work, but I think something even more specific would be nicer like EOF or END (in Scanner or Parsing).

}
}

public boolean next(char c) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

While reading the code this next method makes me think a different action for this method. When I renamed to isNext(...) it more senses to me. what do you think?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, it's really a nextIf. What I like about next is that it reads well in code like if (scanner.next('x')).

With isNext it's not clear that it advances the scanner state to me.

}

public static Builder builder() {
return new Builder(new BitSet());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Builderclass could have this boot.

return new Builder();
...
 private Builder() {
            this.set = new BitSet();
        }

scanner.next();

if (scanner.next('C') && scanner.next('D') && scanner.next('A') && scanner.next('T') && scanner.next('A')
&& scanner.next('[')) {

@samukce samukce Aug 3, 2020 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To have an overload next(...) method for multiple char could increase even more the readability here.

Something like

    if (scanner.next('C', 'D', 'A', 'T', 'A', '[') {
   ....

    //Scanner.java
    public boolean next(char... c) {
        for (char character: c) {
            if (!next(character)){
                return false;
            }
        }
        return true;
    }

   //ScannerTest.java
    @Test
    public void testIsNextMultipleCharacter() {
        Scanner scanner = new Scanner(Collections.<CharSequence>singletonList("foo bar"), 0, 0);
        assertTrue(scanner.next('f', 'o', 'o', ' ', 'b', 'a', 'r'));
        assertFalse(scanner.next('r'));
    }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I was considering this before, maybe even a next(String) instead, which only advances if it finds the whole string.

Comment on lines +25 to +33
if (next == '\n') {
scanner.next();
return ParsedInline.of(new HardLineBreak(), scanner.position());
} else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) {
scanner.next();
return ParsedInline.of(new Text(String.valueOf(next)), scanner.position());
} else {
return ParsedInline.of(new Text("\\"), scanner.position());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just to simplify those if/else and have less indentation.

Suggested change
if (next == '\n') {
scanner.next();
return ParsedInline.of(new HardLineBreak(), scanner.position());
} else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) {
scanner.next();
return ParsedInline.of(new Text(String.valueOf(next)), scanner.position());
} else {
return ParsedInline.of(new Text("\\"), scanner.position());
}
if (next == '\n') {
scanner.next();
return ParsedInline.of(new HardLineBreak(), scanner.position());
}
if (ESCAPABLE.matcher(String.valueOf(next)).matches()) {
scanner.next();
return ParsedInline.of(new Text(String.valueOf(next)), scanner.position());
}
return ParsedInline.of(new Text("\\"), scanner.position());

sb.append('\n');

// Lines between begin and end (we are appending the full line)
for (int line = begin.lineIndex + 1; line < end.lineIndex; line++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We do have an attribute in this line called line too. We may call here by simple i,j,....


public Builder c(char c) {
if (c > 127) {
throw new IllegalArgumentException("Can only match ASCII characters");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
throw new IllegalArgumentException("Can only match ASCII characters");
throw new IllegalArgumentException("Can only match ASCII Printable Characters");

*/
void parse(String input, Node node);
// TODO: Should we use a better type here, one that will be able to include source positions?
void parse(List<CharSequence> lines, Node node);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm happy with this PR now (for internal API, would still need some more cleanup to be public API), but I still want to spike what this type would look like with source positions, so that we can avoid breaking API twice.

The end goal is to extract all current inline parsers, and then allow
customizing inline parsing via the same infrastructure (similar to the
current block parsing).

A stretch goal is to move away from the parser input having to be a
single contiguous String. This could speed up parsing and allow keeping
track of source positions (line and column indexes) for inline nodes.
Most complex one yet, but I think it's nice to get rid of the regexes.
Getting really close to not needing `match` in InlineParserImpl anymore!
This is a big one, and was only possible because of the new Scanner
infrastructure. This has a couple of advantages:

* Less string copying and garbage because we no longer need to have all
  of a block's content in a single contiguous string, we can keep the lines
  we use for block parsing directly
* Because we have individual lines, we could also keep source positions
  and get source positions for inline nodes :wow:!
This avoids a lot of copying (we still need to copy the position
though). In exchange, we need to be more careful and reset the position
if we want to backtrack.
@robinst
robinst merged commit c44a223 into master Sep 8, 2020
@robinst
robinst deleted the inline-parsing branch September 8, 2020 07:04
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