ECS + ASTGen: simplify mapping, rename SourceMapping to TextTransform

This commit is contained in:
Jakub Valtar
2016-04-26 11:33:41 +02:00
parent 852c691fc2
commit e8856e9d6d
6 changed files with 353 additions and 303 deletions

View File

@@ -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(".");

View File

@@ -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

View File

@@ -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;

View File

@@ -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 + '\'') : "") +
'}';
}
}
}

View File

@@ -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();
}
}

View 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);
}
}
}