-
Notifications
You must be signed in to change notification settings - Fork 82
#2346 redone: a formatter for Rascal itself, plus the necessary improvements and stabilization in the Box formatter framework code. #2738
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
Open
jurgenvinju
wants to merge
20
commits into
main
Choose a base branch
from
second-try-rascal-formatter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
b714065
bumped to 0.43.0 to have something to work with
jurgenvinju 11943a8
merged PR #2346 into this one to clean up git-filter-repo mess
jurgenvinju 06681f2
Update ErrorTreeSemanticsTests.rsc
jurgenvinju b9179bc
regenerated parser for rascal because constructor names were added in…
jurgenvinju 7a70844
fixed static errors
jurgenvinju 2169f61
fixed broken and ambiguous links in formatter docs
jurgenvinju 09321d6
fixes more links in doc
jurgenvinju a2db19c
Merge branch 'main' into second-try-rascal-formatter
jurgenvinju c0b28b7
added batch processing tools for formatters
jurgenvinju 494155c
added voidJob functions for consistency and completeness sake
jurgenvinju 4748ac9
Merge branch 'main' into second-try-rascal-formatter
jurgenvinju 914b6a7
Merge branch 'main' into second-try-rascal-formatter
jurgenvinju 3c12b73
removed commented-out assert and added @pitfalls documentation to mit…
jurgenvinju 27dc35a
Change strip method to private default
jurgenvinju 299d843
Update documentation for IO.rsc
jurgenvinju f528caf
Merge branch 'main' into second-try-rascal-formatter
jurgenvinju b78aa0f
Update ExecuteTextEdits.rsc
jurgenvinju a5864e8
Fix typo in synopsis for ExecuteTextEdits.rsc
jurgenvinju b7fc46a
fixed tutor link
jurgenvinju d0d02e0
cleaning up points pointed out by @toinehartman
jurgenvinju File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ module analysis::diff::edits::HiFiLayoutDiff | |
| extend analysis::diff::edits::HiFiTreeDiff; | ||
| import ParseTree; // this should not be necessary because imported by HiFiTreeDiff | ||
| import String; // this should not be be necessary because imported by HiFiTreeDiff | ||
|
jurgenvinju marked this conversation as resolved.
Outdated
|
||
| import lang::rascal::grammar::definition::Characters; | ||
| import IO; | ||
|
|
||
| @synopsis{Normalization choices for case-insensitive literals.} | ||
| data CaseInsensitivity | ||
|
|
@@ -56,7 +58,7 @@ list[TextEdit] layoutDiff(Tree original, Tree formatted, bool recoverComments = | |
| list[TextEdit] rec( | ||
| t:appl(prod(Symbol tS, _, _), list[Tree] tArgs), // layout is not necessarily parsed with the same rules (i.e. comments are lost!) | ||
| u:appl(prod(Symbol uS, _, _), list[Tree] uArgs)) | ||
| = [replace(t@\loc, recoverComments ? learnComments(t, u) : "<u>") | tArgs != uArgs, "<t>" != "<u>" /* avoid useless edits */] | ||
| = [replace(t@\loc, repl) | tArgs != uArgs, str repl := (recoverComments ? learnComments(t, u) : "<u>"), repl != "<t>" /* do not edit anything if nothing has changed */] | ||
| when | ||
| delabel(tS) is layouts, | ||
| delabel(uS) is layouts, | ||
|
|
@@ -106,22 +108,31 @@ list[TextEdit] layoutDiff(Tree original, Tree formatted, bool recoverComments = | |
| default list[TextEdit] rec( | ||
| Tree t:appl(Production p, list[Tree] argsA), | ||
| appl(p /* must be the same by the above assert */, list[Tree] argsB)) | ||
| = [*rec(a, b) | <a, b> <- zip2(argsA, argsB)]; | ||
| = [*rec(argsA[i], argsB[i]) | i <- [0..size(argsA)]]; | ||
|
|
||
| // first add required locations to layout nodes | ||
| original = reposition(original, markLit=true, markLayout=true, markSubLayout=true); | ||
| // TODO: check if indeed repositioning is never needed | ||
| // original = reposition(original, markLit=true, markLayout=true, markSubLayout=true); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we do an remove this TODO? |
||
|
|
||
| return rec(original, formatted); | ||
| } | ||
|
|
||
| private Symbol newlineClass = \char-class([range(10,10)]); | ||
|
|
||
| @synopsis{Make sure the new layout still contains all the source code comments of the original layout} | ||
| @description{ | ||
| This algorithm uses the @category("Comments") tag to detect source code comments inside layout substrings. If the original | ||
| This algorithm uses the `@category(/[cC]omments/)` tag to detect source code comments inside layout substrings. If the original | ||
| layout contains comments, we re-introduce the comments at the expected level of indentation. New comments present in the | ||
| replacement are kept and will overwrite any original comments. | ||
|
|
||
| This trick is complicated by the syntax of multiline comments and single line comments that have | ||
| to end with a newline. | ||
| There are corner cases with respect to the original comments: | ||
| * the single line comment that does not end with a newline itself, yet it must always end with a newline after it. | ||
| * multiple single line comments after each other | ||
|
|
||
| Then there are corner cases with respect to the replacement whitespace: | ||
| * the last line of the replacement whitespace is special. This is the indentation to use for all comments. | ||
| * but there could be no newlines in the replacement whitespace; and still there is a single line comment to be included. | ||
| Now we need to infer an indentation level for what follows the comment from "thin air". | ||
| } | ||
| @benefits{ | ||
| * if comments are kepts and formatted by tools like Tree2Box, then this algorithm does not overwrite these. | ||
|
|
@@ -132,7 +143,14 @@ to end with a newline. | |
| * if comments are not marked with `@category("Comment")` in the original grammar, then this algorithm recovers nothing. | ||
| } | ||
| private str learnComments(Tree original, Tree replacement) { | ||
| originalComments = ["<c>" | /c:appl(prod(_,_,{\tag("category"(/^[Cc]omment$/)), *_}), _) := original]; | ||
| bool mustEndWithNewline(lit("\n")) = true; | ||
| bool mustEndWithNewline(conditional(Symbol s, _)) = mustEndWithNewline(s); | ||
| // if a comment can not contain newline characters, but everything else, then it must be followed by one: | ||
| bool mustEndWithNewline(\iter(Symbol cc:\char-class(_))) = intersection(cc, newlineClass) != newlineClass; | ||
| bool mustEndWithNewline(\iter-star(Symbol cc:\char-class(_))) = intersection(cc, newlineClass) != newlineClass; | ||
| default bool mustEndWithNewline(_) = false; | ||
|
|
||
| originalComments = [<s, s[-1] == "\n" || mustEndWithNewline(lastSym)> | /c:appl(prod(_,[*_,Symbol lastSym],{\tag("category"(/^[Cc]omment$/)), *_}), _) := original, str s := "<c>"]; | ||
|
|
||
| if (originalComments == []) { | ||
| // if the original did not contain comments, stick with the replacements | ||
|
|
@@ -146,23 +164,42 @@ private str learnComments(Tree original, Tree replacement) { | |
| return "<replacement>"; | ||
| } | ||
|
|
||
| // At this point, we know that: (a) comments are not present in the replacement and (b) they used to be there in the original. | ||
| // So the old comments are going to be the new output. however, we want to learn indentation from the replacement. | ||
| // At this point, we know that: | ||
| // (a) comments are not present in the replacement and | ||
| // (b) they used to be there in the original. | ||
| // So the old comments are going to be copied to the new output. | ||
| // But, we want to indent them using the style of the replacement. | ||
|
|
||
| // The last line of the replacement string typically has the indentation for the construct that follows: | ||
| // | // a comment | ||
| // | if (true) { | ||
| // ^^^^ | ||
| // newIndent | ||
| // | ||
| // However, if the replacement string is on a single line, then we don't have the indentation | ||
| // for the string on the next line readily available. In this case we indent the next line | ||
| // to the start column of the replacement layout, as a proxy. | ||
|
|
||
| str replString = "<replacement>"; | ||
| str newIndent = split("\n", replString)[-1] ? ""; | ||
|
|
||
| // Drop the last newline of single-line comments, because we don't want two newlines in the output for every comment: | ||
| str dropEndNl(str line:/^.*\n$/) = (line[..-1]); | ||
| default str dropEndNl(str line) = line; | ||
| if (/\n/ !:= replString) { | ||
| // no newline in the repl string, so no indentation available for what follows the comment... | ||
| newIndent = "<for (_ <- [0..replacement@\loc.begin.column]) {> <}>"; | ||
| } | ||
|
|
||
| // the first line of the replacement ,is the indentation to use. | ||
| str replString = "<replacement>"; | ||
| str replacementIndent = /^\n+$/ !:= replString | ||
| ? split("\n", replString)[0] | ||
| : ""; | ||
|
|
||
| // trimming each line makes sure we forget about the original indentation, and drop accidental spaces after comment lines | ||
| return replString + indent(replacementIndent, | ||
| "<for (c <- originalComments, str line <- split("\n", dropEndNl(c))) {><indent(replacementIndent, trim(line), indentFirstLine=true)> | ||
| '<}>"[..-1], indentFirstLine=false) + replString; | ||
| // we always place sequential comments vertically, because we don't know if we are dealing | ||
| // we a single line comment that has to end with newline by follow restriction or by a literal "\n". | ||
| // TODO: a deeper analysis of the comment rule that's in use could also be used to discover this. | ||
| str trimmedOriginals = "<for (<c, newLine> <- originalComments) {><trim(c)><if (newLine) {> | ||
| '<}><}>"; | ||
|
|
||
| // we wrap the comment with the formatted whitespace to assure the proper indentation | ||
| // of its first line, and the proper indentation of what comes after this layout node | ||
| return replString | ||
| + indent(newIndent, trimmedOriginals, indentFirstLine=false) | ||
| + newIndent | ||
| ; | ||
| } | ||
|
|
||
| private Symbol delabel(label(_, Symbol t)) = t; | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.