mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
ECS + ASTGen: simplify mapping, rename SourceMapping to TextTransform
This commit is contained in:
@@ -2110,12 +2110,12 @@ public class ASTGenerator {
|
||||
|
||||
// If the parsed code contains pde enhancements, take 'em out.
|
||||
// TODO: test this
|
||||
SourceMapping mapping = new SourceMapping();
|
||||
mapping.addAll(SourceUtils.replaceTypeConstructors(pdePhrase));
|
||||
mapping.addAll(SourceUtils.replaceHexLiterals(pdePhrase));
|
||||
mapping.addAll(SourceUtils.replaceColorRegex(pdePhrase));
|
||||
mapping.addAll(SourceUtils.fixFloatsRegex(pdePhrase));
|
||||
String phrase = mapping.apply(pdePhrase);
|
||||
TextTransform transform = new TextTransform(pdePhrase);
|
||||
transform.addAll(SourceUtils.replaceTypeConstructors(pdePhrase));
|
||||
transform.addAll(SourceUtils.replaceHexLiterals(pdePhrase));
|
||||
transform.addAll(SourceUtils.replaceColorRegex(pdePhrase));
|
||||
transform.addAll(SourceUtils.fixFloatsRegex(pdePhrase));
|
||||
String phrase = transform.apply();
|
||||
|
||||
//After typing 'arg.' all members of arg type are to be listed. This one is a flag for it
|
||||
boolean noCompare = phrase.endsWith(".");
|
||||
|
||||
@@ -86,6 +86,7 @@ import processing.data.IntList;
|
||||
import processing.data.StringList;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
import processing.mode.java.preproc.PdePreprocessor.Mode;
|
||||
|
||||
@@ -356,23 +357,18 @@ public class ErrorCheckerService {
|
||||
|
||||
{{ // SYNTAX CHECK
|
||||
|
||||
try {
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
} catch (RuntimeException e) {
|
||||
// Continue normally, comments were scrubbed
|
||||
// Unterminated comment will get caught during syntax check
|
||||
}
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
|
||||
Mode mode = PdePreprocessor.parseMode(workBuffer);
|
||||
|
||||
// Prepare transforms
|
||||
SourceMapping syntaxMapping = new SourceMapping();
|
||||
syntaxMapping.addAll(SourceUtils.insertImports(coreAndDefaultImports));
|
||||
syntaxMapping.addAll(SourceUtils.insertImports(codeFolderImports));
|
||||
syntaxMapping.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
|
||||
syntaxMapping.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
|
||||
syntaxMapping.addAll(SourceUtils.replaceHexLiterals(workBuffer));
|
||||
syntaxMapping.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
|
||||
TextTransform toParsable = new TextTransform(pdeStage);
|
||||
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
|
||||
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
|
||||
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
|
||||
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
|
||||
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
|
||||
toParsable.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
|
||||
|
||||
boolean importsChanged = prevResult == null ||
|
||||
prevResult.classPath == null || prevResult.classLoader == null ||
|
||||
@@ -402,34 +398,35 @@ public class ErrorCheckerService {
|
||||
}
|
||||
|
||||
// Transform code
|
||||
String syntaxStage = syntaxMapping.apply(pdeStage);
|
||||
String parsableStage = toParsable.apply();
|
||||
OffsetMapper parsableMapper = toParsable.getMapper();
|
||||
|
||||
// Create AST
|
||||
CompilationUnit syntaxCU =
|
||||
makeAST(parser, syntaxStage.toCharArray(), COMPILER_OPTIONS);
|
||||
CompilationUnit parsableCU =
|
||||
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
|
||||
|
||||
// Prepare transforms
|
||||
SourceMapping compilationMapping = new SourceMapping();
|
||||
compilationMapping.addAll(SourceUtils.addPublicToTopLevelMethods(syntaxCU));
|
||||
compilationMapping.addAll(SourceUtils.replaceColorAndFixFloats(syntaxCU));
|
||||
TextTransform toCompilable = new TextTransform(parsableStage);
|
||||
toCompilable.addAll(SourceUtils.addPublicToTopLevelMethods(parsableCU));
|
||||
toCompilable.addAll(SourceUtils.replaceColorAndFixFloats(parsableCU));
|
||||
|
||||
// Transform code
|
||||
String javaStage = compilationMapping.apply(syntaxStage);
|
||||
javaStageChars = javaStage.toCharArray();
|
||||
String compilableStage = toCompilable.apply();
|
||||
OffsetMapper compilableMapper = toCompilable.getMapper();
|
||||
javaStageChars = compilableStage.toCharArray();
|
||||
|
||||
// Create AST
|
||||
CompilationUnit compilationCU =
|
||||
CompilationUnit compilableCU =
|
||||
makeASTWithBindings(parser, javaStageChars, COMPILER_OPTIONS,
|
||||
className, result.classPathArray);
|
||||
|
||||
// Update result
|
||||
result.syntaxMapping = syntaxMapping;
|
||||
result.compilationMapping = compilationMapping;
|
||||
result.javaCode = javaStage;
|
||||
result.compilationUnit = compilationCU;
|
||||
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
|
||||
result.javaCode = compilableStage;
|
||||
result.compilationUnit = compilableCU;
|
||||
|
||||
// Get syntax problems
|
||||
List<IProblem> syntaxProblems = Arrays.asList(compilationCU.getProblems());
|
||||
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
|
||||
|
||||
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
|
||||
}}
|
||||
@@ -444,7 +441,7 @@ public class ErrorCheckerService {
|
||||
// TODO: handle error stuff *after* building PreprocessedSketch
|
||||
List<Problem> mappedCompilationProblems =
|
||||
mapProblems(compilationProblems, result.tabStarts, result.pdeCode,
|
||||
result.compilationMapping, result.syntaxMapping);
|
||||
result.offsetMapper);
|
||||
|
||||
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF)) {
|
||||
Map<String, List<Problem>> undefinedTypeProblems = mappedCompilationProblems.stream()
|
||||
@@ -561,7 +558,7 @@ public class ErrorCheckerService {
|
||||
|
||||
protected static List<Problem> mapProblems(List<IProblem> problems,
|
||||
int[] tabStarts, String pdeCode,
|
||||
SourceMapping... mappings) {
|
||||
OffsetMapper mapper) {
|
||||
return problems.stream()
|
||||
// Filter Warnings if they are not enabled
|
||||
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
|
||||
@@ -577,11 +574,9 @@ public class ErrorCheckerService {
|
||||
int start = iproblem.getSourceStart();
|
||||
int stop = iproblem.getSourceEnd(); // inclusive
|
||||
|
||||
// Apply mappings
|
||||
for (SourceMapping mapping : mappings) {
|
||||
start = mapping.getInputOffset(start);
|
||||
stop = mapping.getInputOffset(stop);
|
||||
}
|
||||
// Apply mapping
|
||||
start = mapper.getInputOffset(start);
|
||||
stop = mapper.getInputOffset(stop);
|
||||
|
||||
if (stop < start) {
|
||||
// Should not happen, just to be sure
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.Sketch;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
|
||||
public class PreprocessedSketch {
|
||||
|
||||
@@ -27,8 +28,7 @@ public class PreprocessedSketch {
|
||||
public final String pdeCode;
|
||||
public final String javaCode;
|
||||
|
||||
public final SourceMapping syntaxMapping;
|
||||
public final SourceMapping compilationMapping;
|
||||
public final OffsetMapper offsetMapper;
|
||||
|
||||
public final boolean hasSyntaxErrors;
|
||||
public final boolean hasCompilationErrors;
|
||||
@@ -81,10 +81,7 @@ public class PreprocessedSketch {
|
||||
int tabStartLine = tabIndexToTabStartLine(tabIndex);
|
||||
int pdeLine = tabStartLine + tabLine;
|
||||
int pdeLineOffset = lineToOffset(pdeCode, pdeLine);
|
||||
int javaLineOffset = syntaxMapping.getOutputOffset(pdeLineOffset);
|
||||
if (compilationMapping != null) {
|
||||
javaLineOffset = compilationMapping.getOutputOffset(javaLineOffset);
|
||||
}
|
||||
int javaLineOffset = offsetMapper.getOutputOffset(pdeLineOffset);
|
||||
return offsetToLine(javaCode, javaLineOffset);
|
||||
}
|
||||
|
||||
@@ -93,23 +90,12 @@ public class PreprocessedSketch {
|
||||
int tabStartLine = tabIndexToTabStartLine(tabIndex);
|
||||
int tabStartOffset = lineToOffset(pdeCode, tabStartLine);
|
||||
int pdeOffset = tabStartOffset + tabOffset;
|
||||
int javaOffset = syntaxMapping.getOutputOffset(pdeOffset);
|
||||
if (compilationMapping != null) {
|
||||
javaOffset = compilationMapping.getOutputOffset(javaOffset);
|
||||
}
|
||||
return javaOffset;
|
||||
return offsetMapper.getOutputOffset(pdeOffset);
|
||||
}
|
||||
|
||||
|
||||
public int javaOffsetToPdeOffset(int javaOffset) {
|
||||
int pdeOffset = javaOffset;
|
||||
if (compilationMapping != null) {
|
||||
pdeOffset = compilationMapping.getInputOffset(pdeOffset);
|
||||
}
|
||||
if (syntaxMapping != null) {
|
||||
pdeOffset = syntaxMapping.getInputOffset(pdeOffset);
|
||||
}
|
||||
return pdeOffset;
|
||||
return offsetMapper.getInputOffset(javaOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,8 +143,7 @@ public class PreprocessedSketch {
|
||||
public String pdeCode;
|
||||
public String javaCode;
|
||||
|
||||
public SourceMapping syntaxMapping;
|
||||
public SourceMapping compilationMapping;
|
||||
public OffsetMapper offsetMapper;
|
||||
|
||||
public boolean hasSyntaxErrors;
|
||||
public boolean hasCompilationErrors;
|
||||
@@ -192,8 +177,7 @@ public class PreprocessedSketch {
|
||||
pdeCode = b.pdeCode;
|
||||
javaCode = b.javaCode;
|
||||
|
||||
syntaxMapping = b.syntaxMapping;
|
||||
compilationMapping = b.compilationMapping;
|
||||
offsetMapper = b.offsetMapper != null ? b.offsetMapper : OffsetMapper.EMPTY_MAPPER;
|
||||
|
||||
hasSyntaxErrors = b.hasSyntaxErrors;
|
||||
hasCompilationErrors = b.hasCompilationErrors;
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.core.PApplet;
|
||||
|
||||
import static java.awt.SystemColor.text;
|
||||
|
||||
|
||||
public class SourceMapping {
|
||||
|
||||
private static final Comparator<Edit> INPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.fromOffset, o2.fromOffset);
|
||||
|
||||
private static final Comparator<Edit> OUTPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.toOffset, o2.toOffset);
|
||||
|
||||
private boolean applied;
|
||||
|
||||
private List<Edit> edits = new ArrayList<>();
|
||||
|
||||
private List<Edit> inMap = new ArrayList<>();
|
||||
private List<Edit> outMap = new ArrayList<>();
|
||||
|
||||
|
||||
public void add(Edit edit) {
|
||||
edits.add(edit);
|
||||
}
|
||||
|
||||
|
||||
public void addAll(Collection<Edit> edits) {
|
||||
this.edits.addAll(edits);
|
||||
}
|
||||
|
||||
|
||||
public String apply(CharSequence input) {
|
||||
|
||||
final int inLength = input.length();
|
||||
final StringBuilder output = new StringBuilder(inLength);
|
||||
|
||||
// Make copies of Edits to preserve original edits
|
||||
List<Edit> inEdits = edits.stream().map(Edit::new).collect(Collectors.toList());
|
||||
List<Edit> outEdits = new ArrayList<>(inEdits);
|
||||
|
||||
// Edits sorted by input offsets
|
||||
Collections.sort(inEdits, INPUT_OFFSET_COMP);
|
||||
|
||||
// Edits sorted by output offsets
|
||||
Collections.sort(outEdits, OUTPUT_OFFSET_COMP);
|
||||
|
||||
// TODO: add some validation
|
||||
|
||||
// Input
|
||||
ListIterator<Edit> inIt = inEdits.listIterator();
|
||||
Edit inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
int inEditOff = inEdit == null ? input.length() : inEdit.fromOffset;
|
||||
|
||||
// Output
|
||||
ListIterator<Edit> outIt = outEdits.listIterator();
|
||||
Edit outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
int outEditOff = outEdit == null ? input.length() : outEdit.toOffset;
|
||||
|
||||
int offset = 0;
|
||||
|
||||
inMap.clear();
|
||||
outMap.clear();
|
||||
|
||||
// Walk through the input, apply changes, create mapping
|
||||
while (offset < inLength || inEdit != null || outEdit != null) {
|
||||
|
||||
{ // Copy the unmodified portion of the input, create mapping for it
|
||||
int nextEditOffset = Math.min(inEditOff, outEditOff);
|
||||
|
||||
{ // Insert move block to have mapping for unmodified portions too
|
||||
int length = nextEditOffset - offset;
|
||||
if (length > 0) {
|
||||
Edit ch = Edit.move(offset, length, output.length());
|
||||
inMap.add(ch);
|
||||
outMap.add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the block without changes from the input
|
||||
output.append(input, offset, nextEditOffset);
|
||||
|
||||
// Move offset accordingly
|
||||
offset = nextEditOffset;
|
||||
}
|
||||
|
||||
// Process encountered input edits
|
||||
while (inEdit != null && offset >= inEditOff) {
|
||||
offset += inEdit.fromLength;
|
||||
inMap.add(inEdit);
|
||||
inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
|
||||
}
|
||||
|
||||
// Process encountered output edits
|
||||
while (outEdit != null && offset >= outEditOff) {
|
||||
outEdit.toOffset = output.length();
|
||||
outMap.add(outEdit);
|
||||
if (outEdit.toLength > 0) {
|
||||
if (outEdit.outputText != null) {
|
||||
output.append(outEdit.outputText);
|
||||
} else {
|
||||
output.append(input, outEdit.fromOffset, outEdit.fromOffset + outEdit.fromLength);
|
||||
}
|
||||
}
|
||||
outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
|
||||
}
|
||||
}
|
||||
|
||||
applied = true;
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
|
||||
public int getInputOffset(int outputOffset) {
|
||||
if (Base.DEBUG) checkApplied();
|
||||
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
|
||||
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, outMap.size()-1);
|
||||
Edit edit = outMap.get(i);
|
||||
int diff = outputOffset - edit.toOffset;
|
||||
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
|
||||
}
|
||||
|
||||
|
||||
public int getOutputOffset(int inputOffset) {
|
||||
if (Base.DEBUG) checkApplied();
|
||||
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
|
||||
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, inMap.size()-1);
|
||||
Edit edit = inMap.get(i);
|
||||
int diff = inputOffset - edit.fromOffset;
|
||||
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
applied = false;
|
||||
edits.clear();
|
||||
inMap.clear();
|
||||
outMap.clear();
|
||||
}
|
||||
|
||||
|
||||
private void checkNotApplied() {
|
||||
if (applied) throw new RuntimeException("this mapping was already applied");
|
||||
}
|
||||
|
||||
|
||||
private void checkApplied() {
|
||||
if (!applied) throw new RuntimeException("this mapping was not applied yet");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SourceMapping{" +
|
||||
"edits=" + edits +
|
||||
", applied=" + applied +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
protected static class Edit {
|
||||
|
||||
static Edit insert(int offset, String text) {
|
||||
return new Edit(offset, 0, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit replace(int offset, int length, String text) {
|
||||
return new Edit(offset, length, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit move(int fromOffset, int length, int toOffset) {
|
||||
Edit result = new Edit(fromOffset, length, toOffset, length, null);
|
||||
result.toOffset = toOffset;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Edit delete(int position, int length) {
|
||||
return new Edit(position, length, position, 0, null);
|
||||
}
|
||||
|
||||
Edit(Edit edit) {
|
||||
this.fromOffset = edit.fromOffset;
|
||||
this.fromLength = edit.fromLength;
|
||||
this.toOffset = edit.toOffset;
|
||||
this.toLength = edit.toLength;
|
||||
this.outputText = edit.outputText;
|
||||
}
|
||||
|
||||
Edit(int fromOffset, int fromLength, int toOffset, int toLength, String text) {
|
||||
this.fromOffset = fromOffset;
|
||||
this.fromLength = fromLength;
|
||||
this.toOffset = toOffset;
|
||||
this.toLength = toLength;
|
||||
this.outputText = text;
|
||||
}
|
||||
|
||||
final int fromOffset;
|
||||
final int fromLength;
|
||||
int toOffset;
|
||||
final int toLength;
|
||||
final String outputText;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Edit{" +
|
||||
"from=" + fromOffset + ":" + fromLength +
|
||||
", to=" + toOffset + ":" + toLength +
|
||||
((text != null) ? (", text='" + outputText + '\'') : "") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import processing.mode.java.pdex.SourceMapping.Edit;
|
||||
import processing.mode.java.pdex.TextTransform.Edit;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
|
||||
public class SourceUtils {
|
||||
@@ -299,7 +299,4 @@ public class SourceUtils {
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
310
java/src/processing/mode/java/pdex/TextTransform.java
Normal file
310
java/src/processing/mode/java/pdex/TextTransform.java
Normal file
@@ -0,0 +1,310 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import processing.core.PApplet;
|
||||
|
||||
import static java.awt.SystemColor.text;
|
||||
|
||||
|
||||
public class TextTransform {
|
||||
|
||||
private static final Comparator<Edit> INPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.fromOffset, o2.fromOffset);
|
||||
|
||||
private static final Comparator<Edit> OUTPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.toOffset, o2.toOffset);
|
||||
|
||||
|
||||
private CharSequence input;
|
||||
|
||||
private List<Edit> edits = new ArrayList<>();
|
||||
|
||||
private List<Edit> inMap = new ArrayList<>();
|
||||
private List<Edit> outMap = new ArrayList<>();
|
||||
|
||||
private boolean built;
|
||||
private int builtForLength;
|
||||
|
||||
|
||||
TextTransform(CharSequence input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
|
||||
public void add(Edit edit) {
|
||||
edits.add(edit);
|
||||
built = false;
|
||||
}
|
||||
|
||||
|
||||
public void addAll(Collection<Edit> edits) {
|
||||
this.edits.addAll(edits);
|
||||
built = false;
|
||||
}
|
||||
|
||||
|
||||
public String apply() {
|
||||
final int inLength = input.length();
|
||||
final StringBuilder output = new StringBuilder(inLength);
|
||||
|
||||
buildIfNeeded(inLength);
|
||||
|
||||
outMap.stream()
|
||||
// Filter out Delete edits
|
||||
.filter(outEdit -> outEdit.toLength > 0)
|
||||
.forEach(outEdit -> {
|
||||
if (outEdit.outputText != null) {
|
||||
// Insert or Replace edit
|
||||
output.append(outEdit.outputText);
|
||||
} else {
|
||||
// Move edit
|
||||
output.append(input, outEdit.fromOffset, outEdit.fromOffset + outEdit.fromLength);
|
||||
}
|
||||
});
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
|
||||
public OffsetMapper getMapper() {
|
||||
int inLength = input.length();
|
||||
buildIfNeeded(inLength);
|
||||
return new SimpleOffsetMapper(inMap, outMap);
|
||||
}
|
||||
|
||||
|
||||
private void buildIfNeeded(int inLength) {
|
||||
if (built && inLength == builtForLength) return;
|
||||
|
||||
// Make copies of Edits to preserve original edits
|
||||
List<Edit> inEdits = edits.stream().map(Edit::new).collect(Collectors.toList());
|
||||
List<Edit> outEdits = new ArrayList<>(inEdits);
|
||||
|
||||
// Edits sorted by input offsets
|
||||
Collections.sort(inEdits, INPUT_OFFSET_COMP);
|
||||
|
||||
// Edits sorted by output offsets
|
||||
Collections.sort(outEdits, OUTPUT_OFFSET_COMP);
|
||||
|
||||
// TODO: add some validation
|
||||
|
||||
// Input
|
||||
ListIterator<Edit> inIt = inEdits.listIterator();
|
||||
Edit inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
int inEditOff = inEdit == null ? inLength : inEdit.fromOffset;
|
||||
|
||||
// Output
|
||||
ListIterator<Edit> outIt = outEdits.listIterator();
|
||||
Edit outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
int outEditOff = outEdit == null ? inLength : outEdit.toOffset;
|
||||
|
||||
int inOffset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
inMap.clear();
|
||||
outMap.clear();
|
||||
|
||||
// Walk through the input, apply changes, create mapping
|
||||
while (inOffset < inLength || inEdit != null || outEdit != null) {
|
||||
|
||||
{ // Create mapping for unmodified portion of the input
|
||||
int nextEditOffset = Math.min(inEditOff, outEditOff);
|
||||
|
||||
{ // Insert move block to have mapping for unmodified portions too
|
||||
int length = nextEditOffset - inOffset;
|
||||
if (length > 0) {
|
||||
Edit ch = Edit.move(inOffset, length, outOffset);
|
||||
inMap.add(ch);
|
||||
outMap.add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// Move offsets accordingly
|
||||
outOffset += nextEditOffset - inOffset;
|
||||
inOffset = nextEditOffset;
|
||||
}
|
||||
|
||||
// Process encountered input edits
|
||||
while (inEdit != null && inOffset >= inEditOff) {
|
||||
inOffset += inEdit.fromLength;
|
||||
inMap.add(inEdit);
|
||||
inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
|
||||
}
|
||||
|
||||
// Process encountered output edits
|
||||
while (outEdit != null && inOffset >= outEditOff) {
|
||||
outEdit.toOffset = outOffset;
|
||||
outMap.add(outEdit);
|
||||
outOffset += outEdit.toLength;
|
||||
outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
|
||||
}
|
||||
}
|
||||
|
||||
built = true;
|
||||
builtForLength = inLength;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SourceMapping{" +
|
||||
"edits=" + edits +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
protected static class Edit {
|
||||
|
||||
static Edit insert(int offset, String text) {
|
||||
return new Edit(offset, 0, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit replace(int offset, int length, String text) {
|
||||
return new Edit(offset, length, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit move(int fromOffset, int length, int toOffset) {
|
||||
Edit result = new Edit(fromOffset, length, toOffset, length, null);
|
||||
result.toOffset = toOffset;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Edit delete(int position, int length) {
|
||||
return new Edit(position, length, position, 0, null);
|
||||
}
|
||||
|
||||
Edit(Edit edit) {
|
||||
this.fromOffset = edit.fromOffset;
|
||||
this.fromLength = edit.fromLength;
|
||||
this.toOffset = edit.toOffset;
|
||||
this.toLength = edit.toLength;
|
||||
this.outputText = edit.outputText;
|
||||
}
|
||||
|
||||
Edit(int fromOffset, int fromLength, int toOffset, int toLength, String text) {
|
||||
this.fromOffset = fromOffset;
|
||||
this.fromLength = fromLength;
|
||||
this.toOffset = toOffset;
|
||||
this.toLength = toLength;
|
||||
this.outputText = text;
|
||||
}
|
||||
|
||||
private final int fromOffset;
|
||||
private final int fromLength;
|
||||
private int toOffset;
|
||||
private final int toLength;
|
||||
private final String outputText;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Edit{" +
|
||||
"from=" + fromOffset + ":" + fromLength +
|
||||
", to=" + toOffset + ":" + toLength +
|
||||
((text != null) ? (", text='" + outputText + '\'') : "") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected interface OffsetMapper {
|
||||
int getInputOffset(int outputOffset);
|
||||
int getOutputOffset(int inputOffset);
|
||||
OffsetMapper thenMapping(OffsetMapper mapper);
|
||||
OffsetMapper EMPTY_MAPPER = CompositeOffsetMapper.of();
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleOffsetMapper implements OffsetMapper {
|
||||
private List<Edit> inMap = new ArrayList<>();
|
||||
private List<Edit> outMap = new ArrayList<>();
|
||||
|
||||
private SimpleOffsetMapper(List<Edit> inMap, List<Edit> outMap) {
|
||||
this.inMap.addAll(inMap);
|
||||
this.outMap.addAll(outMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputOffset(int outputOffset) {
|
||||
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
|
||||
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, outMap.size()-1);
|
||||
Edit edit = outMap.get(i);
|
||||
int diff = outputOffset - edit.toOffset;
|
||||
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputOffset(int inputOffset) {
|
||||
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
|
||||
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, inMap.size()-1);
|
||||
Edit edit = inMap.get(i);
|
||||
int diff = inputOffset - edit.fromOffset;
|
||||
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetMapper thenMapping(OffsetMapper mapper) {
|
||||
return CompositeOffsetMapper.of(this, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class CompositeOffsetMapper implements OffsetMapper {
|
||||
private List<OffsetMapper> mappers = new ArrayList<>();
|
||||
|
||||
public static CompositeOffsetMapper of(OffsetMapper... inMappers) {
|
||||
CompositeOffsetMapper composite = new CompositeOffsetMapper();
|
||||
|
||||
// Add mappers one by one, unwrap if Composite
|
||||
for (OffsetMapper mapper : inMappers) {
|
||||
if (mapper instanceof CompositeOffsetMapper) {
|
||||
composite.mappers.addAll(((CompositeOffsetMapper) mapper).mappers);
|
||||
} else {
|
||||
composite.mappers.add(mapper);
|
||||
}
|
||||
}
|
||||
|
||||
return composite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputOffset(int outputOffset) {
|
||||
for (int i = mappers.size() - 1; i >= 0; i--) {
|
||||
outputOffset = mappers.get(i).getInputOffset(outputOffset);
|
||||
}
|
||||
return outputOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputOffset(int inputOffset) {
|
||||
for (OffsetMapper mapper : mappers) {
|
||||
inputOffset = mapper.getOutputOffset(inputOffset);
|
||||
}
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetMapper thenMapping(OffsetMapper mapper) {
|
||||
return CompositeOffsetMapper.of(this, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user