Skip to content

Commit 19e9251

Browse files
committed
Diff algorithm: handle whitespace better
We now use the old position of a class which in some cases makes whitespace appear on the correct position beside the class. A testcase based on Buildings was added.
1 parent 05d73fb commit 19e9251

File tree

8 files changed

+242
-40
lines changed

8 files changed

+242
-40
lines changed

OMCompiler/Compiler/Parsers/SimpleModelicaParser.mo

Lines changed: 158 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ function component_clause
692692
protected
693693
TokenId id;
694694
Boolean b;
695+
list<ParseTree> nodeNames;
695696
algorithm
696697
(tokens, tree) := type_prefix(tokens, tree);
697698
(tokens, tree) := type_specifier(tokens, tree);
@@ -700,8 +701,8 @@ algorithm
700701
(tokens, tree) := array_subscripts(tokens, tree);
701702
end if;
702703
tree := makeNode(listReverse(tree), label=LEAF(makeToken(TokenId.IDENT, "$type_specifier")))::{};
703-
(tokens, tree) := component_list(tokens, tree);
704-
nodeName := LEAF(makeToken(TokenId.IDENT, "$component"));
704+
(tokens, tree, nodeNames) := component_list(tokens, tree);
705+
nodeName := LEAF(makeToken(TokenId.IDENT, "$component:"+stringDelimitList(list(parseTreeStr(name::{}) for name in nodeNames),",")));
705706
outTree := makeNodePrependTree(listReverse(tree), inTree, label=nodeName);
706707
end component_clause;
707708

@@ -789,18 +790,21 @@ end subscript;
789790

790791
function component_list
791792
extends partialParser;
793+
output list<ParseTree> nodeNames={};
792794
protected
793795
TokenId id;
794796
Boolean b;
795797
ParseTree nodeName;
796798
algorithm
797-
(tokens, tree) := component_declaration(tokens, tree);
799+
(tokens, tree, nodeName) := component_declaration(tokens, tree);
800+
nodeNames := nodeName::nodeNames;
798801
while true loop
799802
(tokens, tree, b) := scanOpt(tokens, tree, TokenId.COMMA);
800803
if not b then
801804
break;
802805
end if;
803-
(tokens, tree) := component_declaration(tokens, tree);
806+
(tokens, tree, nodeName) := component_declaration(tokens, tree);
807+
nodeNames := nodeName::nodeNames;
804808
end while;
805809
outTree := makeNodePrependTree(listReverse(tree), inTree);
806810
end component_list;
@@ -2004,12 +2008,14 @@ function treeDiffWork
20042008
input CmpParseTreeFunc compare;
20052009
output list<tuple<Diff,list<ParseTree>>> res, resLocal;
20062010
protected
2007-
list<ParseTree> before, middle, after, addedTrees, deletedTrees;
2011+
list<ParseTree> t2_strip, before, middle, after, addedTrees, deletedTrees, ts;
2012+
list<String> addList, delList;
20082013
Integer nadd, ndel;
20092014
ParseTree addedTree, deletedTree, addedLabel, deletedLabel, deleted;
2010-
Boolean addedBeforeDeleted, joinTrees;
2015+
Boolean addedBeforeDeleted, joinTrees, tryFind;
20112016
tuple<Diff,list<ParseTree>> diff1;
2012-
String debugString1="", debugString2="";
2017+
String str, debugString1="", debugString2="";
2018+
Diff d;
20132019
algorithm
20142020
// Speed-up. No deep compare for single nodes...
20152021
_ := match (t1, t2)
@@ -2030,6 +2036,11 @@ algorithm
20302036
then ();
20312037
else ();
20322038
end match;
2039+
if parseTreeIsNewLine(List.first(t2)) then
2040+
t2_strip := listRest(t2);
2041+
else
2042+
t2_strip := t2;
2043+
end if;
20332044
if debug then
20342045
print("Do diff at depth="+String(depth)+", len(t1)="+String(listLength(t1))+", len(t2)="+String(listLength(t2))+"\n");
20352046
print("top t1="+firstTokenDebugStr(t1)+"\n");
@@ -2129,23 +2140,52 @@ algorithm
21292140
print("number of labeled nodes. add="+String(listLength(addedTrees))+" del="+String(listLength(deletedTrees))+"\n");
21302141
print(DiffAlgorithm.printDiffTerminalColor(res, parseTreeNodeStr) + "\n");
21312142
end if;
2143+
// We need to know if the labels changed order; if they didn't, we can improve the diff
2144+
for x in res loop
2145+
(d,ts) := x;
2146+
if d==Diff.Equal then
2147+
continue;
2148+
end if;
2149+
addList := {};
2150+
delList := {};
2151+
for t in ts loop
2152+
if isEmpty(t) or parseTreeIsWhitespace(t) or isEmpty(nodeLabel(t))then
2153+
continue;
2154+
end if;
2155+
str := parseTreeStr(nodeLabel(t)::{});
2156+
if d==Diff.Add then
2157+
addList := str::addList;
2158+
else
2159+
delList := str::delList;
2160+
end if;
2161+
end for;
2162+
end for;
21322163
// O(D*D)
21332164
for added in addedTrees loop
2165+
tryFind := false;
21342166
try
21352167
(deleted, deletedTrees) := List.findAndRemove1(deletedTrees, function compareNodeLabels(compare=compare), added);
2136-
resLocal := treeDiffWork(getNodes(deleted), getNodes(added), depth+1, compare);
2137-
if debug then
2138-
debugString1:=DiffAlgorithm.printDiffTerminalColor(res, parseTreeNodeStr);
2139-
end if;
2140-
res := replaceLabeledDiff(res, resLocal, nodeLabel(added), compare);
2141-
if debug then
2142-
debugString2:=DiffAlgorithm.printDiffTerminalColor(res, parseTreeNodeStr);
2143-
print("replaceLabeledDiff change for label:" + parseTreeNodeStr(nodeLabel(added)) + "\n");
2144-
print("before replaceLabeledDiff: " + debugString1 + "\n");
2145-
print("after replaceLabeledDiff: " + debugString2 + "\n");
2146-
end if;
21472168
else
2169+
try
2170+
(deleted, deletedTrees) := List.findAndRemove1(deletedTrees, function compareNodeLabelsSpecial(compare=compare,delList=delList), added);
2171+
else
2172+
tryFind := true;
2173+
end try;
21482174
end try;
2175+
if tryFind then
2176+
continue;
2177+
end if;
2178+
resLocal := treeDiffWork(getNodes(deleted), getNodes(added), depth+1, compare);
2179+
if debug then
2180+
debugString1:=DiffAlgorithm.printDiffTerminalColor(res, parseTreeNodeStr);
2181+
end if;
2182+
res := replaceLabeledDiff(res, resLocal, nodeLabel(added), nodeLabel(deleted), compare, labelOrderDidNotChange(addList,delList));
2183+
if debug then
2184+
debugString2:=DiffAlgorithm.printDiffTerminalColor(res, parseTreeNodeStr);
2185+
print("replaceLabeledDiff change for label:" + parseTreeNodeStr(nodeLabel(added)) + "\n");
2186+
print("before replaceLabeledDiff: " + debugString1 + "\n");
2187+
print("after replaceLabeledDiff: " + debugString2 + "\n");
2188+
end if;
21492189
end for;
21502190
else
21512191
// print(DiffAlgorithm.printDiffXml(res, parseTreeNodeStr) + "\n");
@@ -2172,6 +2212,28 @@ algorithm
21722212
b := compare(nodeLabel(t1),nodeLabel(t2));
21732213
end compareNodeLabels;
21742214

2215+
function compareNodeLabelsSpecial
2216+
input ParseTree t1, t2;
2217+
input CmpParseTreeFunc compare;
2218+
input list<String> delList;
2219+
output Boolean b;
2220+
algorithm
2221+
b := nodeLabelIsComponent(t1) and nodeLabelIsComponent(t2) and not listMember(parseTreeStr(nodeLabel(t1)::{}), delList);
2222+
end compareNodeLabelsSpecial;
2223+
2224+
function nodeLabelIsComponent
2225+
input ParseTree t1;
2226+
output Boolean b;
2227+
protected
2228+
String contents;
2229+
algorithm
2230+
b := match nodeLabel(t1)
2231+
case LEAF(token=Token.TOKEN(id=TokenId.IDENT, fileContents=contents))
2232+
then 0==System.strncmp(contents, "$component:", 11);
2233+
else false;
2234+
end match;
2235+
end nodeLabelIsComponent;
2236+
21752237
function filterDiffWhitespace
21762238
input list<tuple<Diff,list<ParseTree>>> inDiff;
21772239
output list<tuple<Diff,list<ParseTree>>> diff;
@@ -2191,6 +2253,10 @@ algorithm
21912253
diff := {};
21922254
firstIter := true;
21932255
while not listEmpty(diffLocal) loop
2256+
// (diffEnum,tree) := List.first(diffLocal);
2257+
// print(String(diffEnum) + ":\n");
2258+
// print(parseTreeStr(tree));
2259+
// print("\n");
21942260
diffLocal := match diffLocal
21952261
// Empty node
21962262
case (_, {})::diffLocal then diffLocal;
@@ -2210,6 +2276,19 @@ algorithm
22102276
algorithm
22112277
diff := (Diff.Equal, tree)::diff1::diff;
22122278
then {};
2279+
// Remove empty sections; they mess with rules further down
2280+
case ((_, tree)::diffLocal)
2281+
guard min(isEmpty(t) for t in tree)
2282+
then diffLocal;
2283+
case (diff1::(_, tree)::diffLocal)
2284+
guard min(isEmpty(t) for t in tree)
2285+
then diff1::diffLocal;
2286+
case ((diffEnum, tree)::diffLocal)
2287+
guard max(isEmpty(t) for t in tree)
2288+
then (diffEnum,list(t for t guard not isEmpty(t) in tree))::diffLocal;
2289+
case (diff1::(diffEnum, tree)::diffLocal)
2290+
guard max(isEmpty(t) for t in tree)
2291+
then diff1::(diffEnum,list(t for t guard not isEmpty(t) in tree))::diffLocal;
22132292
// Sometimes a comment may move between 2 trees. This can bring it back, keeping the diff smaller.
22142293
case (Diff.Delete,tree1)::(diff2 as (_, tree2))::(diff3 as (_, tree3))::(Diff.Add, tree4First::tree4)::diffLocal
22152294
guard min(parseTreeIsWhitespaceNotComment(t) for t in tree2) and
@@ -2220,6 +2299,11 @@ algorithm
22202299
algorithm
22212300
diff := (Diff.Equal, {LEAF(makeToken(TokenId.WHITESPACE, " "))})::diff1::diff;
22222301
then diffLocal;
2302+
case ((diff1 as (Diff.Equal,tree1 as (_::_)))::(diff2 as (Diff.Add, tree2 as (_::_)))::diffLocal)
2303+
guard needsWhitespaceBetweenTokens(lastToken(List.last(tree1)), firstTokenInTree(listGet(tree2, 1)))
2304+
algorithm
2305+
diffLocal := diff1::(Diff.Equal, {LEAF(makeToken(TokenId.WHITESPACE, " "))})::diff2::diffLocal;
2306+
then diffLocal;
22232307
case ((diff1 as (Diff.Equal,tree1 as (_::_)))::(Diff.Delete, tree)::(diffLocal as ((Diff.Equal,tree2 as (_::_))::_)))
22242308
algorithm
22252309
diff := diff1::diff;
@@ -2233,6 +2317,12 @@ algorithm
22332317
algorithm
22342318
diff := diff1::diff;
22352319
then diffLocal;
2320+
// EQ(NEWLINE) + ADD(NEWLINE, ...) => EQ(NEWLINE) + ADD(...)
2321+
case ((diff1 as (Diff.Equal,tree1))::(Diff.Add, tree2)::diffLocal)
2322+
guard parseTreeIsNewLine(List.last(tree1)) and parseTreeIsNewLine(List.first(tree2))
2323+
algorithm
2324+
print("New case\n");
2325+
then diff1::(Diff.Add, listRest(tree2))::diffLocal;
22362326
// NEWLINE ADD WS DEL => NEWLINE WS ADD DEL
22372327
case (diff1 as (Diff.Equal,tree1))::(diff2 as (Diff.Add, tree2))::(diff3 as (Diff.Equal, tree3))::(diff4 as (Diff.Delete, tree4First::tree4))::diffLocal
22382328
guard parseTreeIsNewLine(List.last(tree1)) and min(parseTreeIsWhitespaceNotComment(t) for t in tree3)
@@ -2352,6 +2442,39 @@ algorithm
23522442
diff := listReverseInPlace(diffLocal);
23532443
end filterDiffWhitespace;
23542444

2445+
function labelOrderDidNotChange
2446+
input list<String> addList, delList;
2447+
output Boolean b;
2448+
protected
2449+
list<String> acc = {}, del = delList;
2450+
String s;
2451+
algorithm
2452+
b := false;
2453+
for item in addList loop
2454+
if listMember(item, acc) then
2455+
return;
2456+
end if;
2457+
if listMember(item, del) then
2458+
while item <> List.first(del) loop
2459+
s::del := del;
2460+
if listMember(s, acc) then
2461+
return;
2462+
end if;
2463+
acc := s::acc;
2464+
end while;
2465+
del := listRest(del);
2466+
end if;
2467+
acc := item::acc;
2468+
end for;
2469+
for item in delList loop
2470+
if listMember(item, acc) then
2471+
return;
2472+
end if;
2473+
acc := item::acc;
2474+
end for;
2475+
b := true;
2476+
end labelOrderDidNotChange;
2477+
23552478
function makeToken
23562479
input TokenId id;
23572480
input String str;
@@ -2363,26 +2486,33 @@ end makeToken;
23632486

23642487
function replaceLabeledDiff
23652488
input list<tuple<Diff,list<ParseTree>>> inDiff, diffedNodes;
2366-
input ParseTree labelOfDiffedNodes;
2489+
input ParseTree labelOfDiffedAddedNodes, labelOfDiffedDeletedNodes;
23672490
input CmpParseTreeFunc compare;
2491+
input Boolean inAllLabelsAreInOrder "If all labels are in order, there are no moves and we can replace the deleted node which keeps whitespace in better positions";
23682492
output list<tuple<Diff,list<ParseTree>>> res={};
23692493
protected
23702494
list<tuple<Diff,list<ParseTree>>> filtered;
2371-
list<ParseTree> lst, acc;
2372-
Boolean found=false;
2495+
list<ParseTree> lst, acc, tmp;
2496+
Boolean found=false, allLabelsAreInOrder=inAllLabelsAreInOrder;
2497+
Diff d;
2498+
Token t1,t2;
23732499
algorithm
2500+
if parseTreeStr(labelOfDiffedDeletedNodes::{}) == "$equation_section" then
2501+
allLabelsAreInOrder := false;
2502+
end if;
23742503
for diff in inDiff loop
23752504
res := match diff
23762505
case (Diff.Equal, _) then diff::res;
2377-
case (Diff.Add, lst) guard not max(compare(nodeLabel(t), labelOfDiffedNodes) for t in lst) then diff::res;
2378-
case (Diff.Delete, lst) guard not max(compare(nodeLabel(t), labelOfDiffedNodes) for t in lst) then diff::res;
2379-
case (Diff.Delete, lst) then (Diff.Delete, list(t for t guard not compare(nodeLabel(t), labelOfDiffedNodes) in lst))::res; // TODO: Handle the deletion better...
2380-
case (Diff.Add, lst)
2506+
case (Diff.Add, lst) guard not max(compare(nodeLabel(t), labelOfDiffedAddedNodes) for t in lst) then diff::res;
2507+
case (Diff.Delete, lst) guard not max(compare(nodeLabel(t), labelOfDiffedDeletedNodes) for t in lst) then diff::res;
2508+
case (Diff.Add, lst) guard allLabelsAreInOrder then (Diff.Add, list(t for t guard not compare(nodeLabel(t), labelOfDiffedAddedNodes) in lst))::res; // TODO: Handle the deletion better...
2509+
case (Diff.Delete, lst) guard not allLabelsAreInOrder then (Diff.Delete, list(t for t guard not compare(nodeLabel(t), labelOfDiffedDeletedNodes) in lst))::res; // TODO: Handle the deletion better...
2510+
case (d, lst)
23812511
algorithm
23822512
acc := {};
23832513
for t in lst loop
23842514
// Assuming adjacent to the delete node
2385-
if (not found) and compare(nodeLabel(t), labelOfDiffedNodes) then
2515+
if (not found) and compare(nodeLabel(t), if allLabelsAreInOrder then labelOfDiffedDeletedNodes else labelOfDiffedAddedNodes) then
23862516
if not listEmpty(acc) then
23872517
res := (Diff.Add, listReverse(acc))::res;
23882518
acc := {};
@@ -2392,7 +2522,7 @@ algorithm
23922522
res := listAppend(filtered, res);
23932523
found := true;
23942524
else
2395-
res := (Diff.Add, {t})::res;
2525+
res := (d, {t})::res;
23962526
end if;
23972527
end for;
23982528
if not listEmpty(acc) then
@@ -2605,7 +2735,7 @@ function firstTokenInTree
26052735
output Token token;
26062736
algorithm
26072737
token := match t
2608-
case EMPTY() then fail();
2738+
case EMPTY() algorithm print("No first token in tree\n"); then fail();
26092739
case LEAF() then t.token;
26102740
case NODE() then firstTokenInTree(listGet(t.nodes, 1));
26112741
end match;
@@ -2878,13 +3008,6 @@ constant list<TokenId> tokenIdsComment = {
28783008
TokenId.BLOCK_COMMENT
28793009
};
28803010

2881-
function dummyParseTreeIsWhitespaceFalse
2882-
// The diff-algorithm will strip leading whitespace, but these are
2883-
// sort of significant...
2884-
input ParseTree t1;
2885-
output Boolean b=false;
2886-
end dummyParseTreeIsWhitespaceFalse;
2887-
28883011
function parseTreeIsWhitespace
28893012
input ParseTree t1;
28903013
output Boolean b;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TwoWayFlowElementBuoyancy.diff
2+
TwoWayFlowElementBuoyancyMerged.mo

testsuite/openmodelica/diff/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ticket4153.mos \
2525
ticket4368.mos \
2626
ticket5360.mos \
2727
ticket5949.mos \
28+
TwoWayFlowElementBuoyancy.mos \
2829
UTF8.mos \
2930
ticket4781.mos
3031

0 commit comments

Comments
 (0)