diff --git a/drjava/.gitignore b/drjava/.gitignore index 17fc5bb15..082617f10 100644 --- a/drjava/.gitignore +++ b/drjava/.gitignore @@ -1,3 +1,5 @@ *.jar +*.class +*.java~ classes/ diff --git a/drjava/lib/ecj-3.6M7.jar b/drjava/lib/ecj-3.6M7.jar deleted file mode 100644 index d965f9e12..000000000 Binary files a/drjava/lib/ecj-3.6M7.jar and /dev/null differ diff --git a/drjava/src/edu/rice/cs/drjava/IndentFiles.java b/drjava/src/edu/rice/cs/drjava/IndentFiles.java index cdbc8abfa..ae3437255 100644 --- a/drjava/src/edu/rice/cs/drjava/IndentFiles.java +++ b/drjava/src/edu/rice/cs/drjava/IndentFiles.java @@ -41,7 +41,7 @@ // TODO: Change the usage of these classes to Collections style. // TODO: Do these need to be synchronized? import edu.rice.cs.plt.io.IOUtil; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.definitions.DefinitionsDocument; import edu.rice.cs.drjava.model.GlobalEventNotifier; diff --git a/drjava/src/edu/rice/cs/drjava/model/AbstractDJDocument.java b/drjava/src/edu/rice/cs/drjava/model/AbstractDJDocument.java index d49731d5e..eff040488 100644 --- a/drjava/src/edu/rice/cs/drjava/model/AbstractDJDocument.java +++ b/drjava/src/edu/rice/cs/drjava/model/AbstractDJDocument.java @@ -41,7 +41,7 @@ import edu.rice.cs.drjava.config.OptionEvent; import edu.rice.cs.drjava.config.OptionListener; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.definitions.reducedmodel.BraceInfo; import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl; import edu.rice.cs.drjava.model.definitions.reducedmodel.HighlightStatus; @@ -198,14 +198,14 @@ private void _initNewIndenter() { _listener1 = new OptionListener() { public void optionChanged(OptionEvent oce) { // System.err.println("Changing INDENT_INC for " + this + " to " + oce.value); - indenter.buildTree(oce.value); + indenter.build(oce.value); } }; _listener2 = new OptionListener() { public void optionChanged(OptionEvent oce) { // System.err.println("Reconfiguring indenter to use AUTO_CLOSE_COMMENTS = " + oce.value); - indenter.buildTree(DrJava.getConfig().getSetting(INDENT_INC)); + indenter.build(DrJava.getConfig().getSetting(INDENT_INC)); } }; @@ -960,6 +960,9 @@ public void indentLines(int selStart, int selEnd, Indenter.IndentReason reason, // Begins a compound edit. // int key = startCompoundEdit(); // commented out in connection with the FrenchKeyBoard Fix + // Store the intermediate states of the indenter + Indenter.RunningState state = null; + try { if (selStart == selEnd) { // single line to indent // Utilities.showDebug("selStart = " + selStart + " currentLocation = " + _currentLocation); @@ -969,8 +972,7 @@ public void indentLines(int selStart, int selEnd, Indenter.IndentReason reason, setCurrentLocation(lineStart); // Indent, updating current location if necessary. // Utilities.showDebug("Indenting line at offset " + selStart); - _indentLine(reason); - + state = _indentLine(reason, state); setCurrentLocation(oldPosition.getOffset()); // moves currentLocation back to original offset on line if (onlySpacesBeforeCurrent()) move(_getWhiteSpace()); // passes any additional spaces before firstNonWS } @@ -997,6 +999,9 @@ private void _indentBlock(final int start, final int end, Indenter.IndentReason _queryCache = new HashMap(INIT_CACHE_SIZE); _offsetToQueries = new TreeMap>(); + // Store the intermediate states of the indenter + Indenter.RunningState state = null; + // Keep marker at the end. This Position will be the correct endpoint no matter how we change // the doc doing the indentLine calls. final Position endPos = this.createUnwrappedPosition(end); @@ -1009,7 +1014,8 @@ private void _indentBlock(final int start, final int end, Indenter.IndentReason Position walkerPos = this.createUnwrappedPosition(walker); // Indent current line // We ignore current location info from each line, because it probably doesn't make sense in a block context. - _indentLine(reason); // this operation is atomic; boolean result is discarded + + state = _indentLine(reason, state); // this operation is atomic // Move back to walker spot setCurrentLocation(walkerPos.getOffset()); walker = walkerPos.getOffset(); @@ -1032,6 +1038,8 @@ private void _indentBlock(final int start, final int end, Indenter.IndentReason /** Indents a line using the Indenter. Public ONLY for testing purposes. */ public void _indentLine(Indenter.IndentReason reason) { getIndenter().indent(this, reason); } + + private Indenter.RunningState _indentLine(Indenter.IndentReason reason, Indenter.RunningState state) { return getIndenter().indent(this, reason, state); }; /** Returns the "intelligent" beginning of line. If currPos is to the right of the first * non-whitespace character, the position of the first non-whitespace character is returned. diff --git a/drjava/src/edu/rice/cs/drjava/model/AbstractGlobalModel.java b/drjava/src/edu/rice/cs/drjava/model/AbstractGlobalModel.java index 2ade308a8..cde60066d 100644 --- a/drjava/src/edu/rice/cs/drjava/model/AbstractGlobalModel.java +++ b/drjava/src/edu/rice/cs/drjava/model/AbstractGlobalModel.java @@ -106,7 +106,7 @@ import edu.rice.cs.drjava.model.definitions.DefinitionsEditorKit; import edu.rice.cs.drjava.model.definitions.DocumentUIListener ; import edu.rice.cs.drjava.model.definitions.InvalidPackageException; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.definitions.reducedmodel.HighlightStatus; import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelControl; import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState; diff --git a/drjava/src/edu/rice/cs/drjava/model/DJDocument.java b/drjava/src/edu/rice/cs/drjava/model/DJDocument.java index f3edfad8b..e31daa061 100644 --- a/drjava/src/edu/rice/cs/drjava/model/DJDocument.java +++ b/drjava/src/edu/rice/cs/drjava/model/DJDocument.java @@ -39,7 +39,7 @@ import edu.rice.cs.drjava.model.definitions.reducedmodel.*; import edu.rice.cs.util.text.SwingDocumentInterface; import edu.rice.cs.util.OperationCanceledException; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import java.util.ArrayList; import javax.swing.text.AttributeSet; diff --git a/drjava/src/edu/rice/cs/drjava/model/DummyOpenDefDoc.java b/drjava/src/edu/rice/cs/drjava/model/DummyOpenDefDoc.java index 5a75360f2..b47d8c691 100644 --- a/drjava/src/edu/rice/cs/drjava/model/DummyOpenDefDoc.java +++ b/drjava/src/edu/rice/cs/drjava/model/DummyOpenDefDoc.java @@ -53,7 +53,7 @@ import edu.rice.cs.drjava.model.definitions.reducedmodel.*; import edu.rice.cs.drjava.model.FinalizationListener; import edu.rice.cs.drjava.model.definitions.ClassNameNotFoundException; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; //import edu.rice.cs.drjava.model.definitions.DefinitionsDocument.WrappedPosition; import edu.rice.cs.util.OperationCanceledException; diff --git a/drjava/src/edu/rice/cs/drjava/model/GlobalIndentTest.java b/drjava/src/edu/rice/cs/drjava/model/GlobalIndentTest.java index 0baadc80c..aee4c0e01 100644 --- a/drjava/src/edu/rice/cs/drjava/model/GlobalIndentTest.java +++ b/drjava/src/edu/rice/cs/drjava/model/GlobalIndentTest.java @@ -39,7 +39,7 @@ import javax.swing.text.BadLocationException; import java.util.List; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.util.OperationCanceledException; /** @@ -196,7 +196,8 @@ public void testIndentInsideParenAtStart() throws BadLocationException, Operatio openDoc.insertString(BAR_CALL_1.length(), BAR_CALL_2, null); openDoc.setCurrentLocation(BAR_CALL_1.length()); int loc = openDoc.getCurrentLocation(); - openDoc.indentLines(loc, loc, Indenter.IndentReason.OTHER, null); + openDoc.indentLines(loc, loc, Indenter.IndentReason.OTHER, null); + System.out.println(BAR_CALL_1 + BAR_CALL_2); _assertContents(BAR_CALL_1 + " " + BAR_CALL_2, openDoc); _assertLocation(BAR_CALL_1.length() + 4, openDoc); } diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/DefinitionsDocument.java b/drjava/src/edu/rice/cs/drjava/model/definitions/DefinitionsDocument.java index cda4c81a6..d65340756 100644 --- a/drjava/src/edu/rice/cs/drjava/model/definitions/DefinitionsDocument.java +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/DefinitionsDocument.java @@ -59,7 +59,7 @@ import edu.rice.cs.util.Log; import edu.rice.cs.util.UnexpectedException; import edu.rice.cs.util.swing.Utilities; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.OpenDefinitionsDocument; import edu.rice.cs.drjava.model.*; diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/IndentTest.java b/drjava/src/edu/rice/cs/drjava/model/definitions/IndentTest.java index b24b7c28b..0e9c8dd8a 100644 --- a/drjava/src/edu/rice/cs/drjava/model/definitions/IndentTest.java +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/IndentTest.java @@ -48,7 +48,7 @@ import edu.rice.cs.drjava.model.DJDocument; import edu.rice.cs.drjava.model.definitions.reducedmodel.BraceInfo; import edu.rice.cs.drjava.config.*; -import edu.rice.cs.drjava.model.definitions.indent.*; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.*; import edu.rice.cs.drjava.model.GlobalEventNotifier; //import edu.rice.cs.util.FileOps; @@ -337,6 +337,8 @@ public void testIndentSwitch() throws BadLocationException { "break;\n" + "}\n"; + // : + String indented = "switch (x) {\n" + " case 1:\n" + // Starting new statement after brace @@ -1605,12 +1607,12 @@ public void testNoBalancedParens() throws BadLocationException { " public void m() {\n" + " _junitLocationEnabledListener = new ConfigOptionListeners.\n" + " RequiresDrJavaRestartListener(this, \"Use External JUnit\"));\n" + - " _junitLocationListener = new ConfigOptionListeners.\n" + - " RequiresDrJavaRestartListener(_configFrame, \"JUnit Location\"));\n" + - " _rtConcJUnitLocationEnabledListener = new ConfigOptionListeners.\n" + - " RequiresInteractionsRestartListener(_configFrame, \"Use ConcJUnit Runtime\"));\n" + - " _rtConcJUnitLocationListener = new ConfigOptionListeners.\n" + - " RequiresInteractionsRestartListener(_configFrame, \"ConcJUnit Runtime Location\"));\n" + + " _junitLocationListener = new ConfigOptionListeners.\n" + + " RequiresDrJavaRestartListener(_configFrame, \"JUnit Location\"));\n" + + " _rtConcJUnitLocationEnabledListener = new ConfigOptionListeners.\n" + + " RequiresInteractionsRestartListener(_configFrame, \"Use ConcJUnit Runtime\"));\n" + + " _rtConcJUnitLocationListener = new ConfigOptionListeners.\n" + + " RequiresInteractionsRestartListener(_configFrame, \"ConcJUnit Runtime Location\"));\n" + " }\n" + "}\n"; diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/Indenter.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/Indenter.java index ddf7bc2ad..748eec03d 100644 --- a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/Indenter.java +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/Indenter.java @@ -45,9 +45,13 @@ */ public class Indenter { + //private NewIndenter newindenter; + public Indenter(int indentLevel) { _indentLevel = indentLevel; - buildTree(indentLevel); + //buildTree(indentLevel); + + //newindenter = new NewIndenter(indentLevel); } protected int _indentLevel; @@ -156,8 +160,10 @@ public void buildTree(int indentLevel) { * @param doc document containing line to be indented Assumes that reduced lock is already held. * @return true if the condition tested by the top rule holds, false otherwise */ - public void indent(AbstractDJDocument doc, Indenter.IndentReason reason) { + //public boolean indent(AbstractDJDocument doc, Indenter.IndentReason reason) { // Utilities.showDebug("Indenter.indent called on doc " + doc); - _topRule.indentLine(doc, reason); - } + //newindenter.indent(doc, reason); + //return true; + //return _topRule.indentLine(doc, reason); + //} } \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Action.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Action.java new file mode 100644 index 000000000..5df4e1769 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Action.java @@ -0,0 +1,16 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * An action for a state transition. + * Calls to apply will modify the input tape and stack to apply the transition logic. + */ +public interface Action +{ + /** + * Apply transition logic to the input tape and stack. + * This is likely to modify the input tape or the stack. + * @param input The current input tape + * @param stack The current context + */ + public void apply(InputTape input, ContextStack stack); +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterContext.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterContext.java new file mode 100644 index 000000000..69bbc4eeb --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterContext.java @@ -0,0 +1,32 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * An action for a state transition. + * Calls to apply will add a new context element to the stack. This context element will increase + * indentation by a fixed amount. + */ +public class ActionEnterContext implements Action { + private int tag; + private int extraIndent; + + /** + * @param tag Tag to use to identify the new context + * @param extraIndent Fixed additional indentation to add with the new context. + */ + public ActionEnterContext(int tag, int extraIndent) { + this.tag = tag; + this.extraIndent = extraIndent; + } + + /** + * Apply transition logic to the input tape and stack. + * In this case, push an additional context element onto the stack. + * + * @param input The current input tape + * @param stack The current context + */ + public void apply(InputTape input, ContextStack stack) { + int currentIndentation = stack.getIndentationLevel(); + stack.push(new ContextStack.ContextSymbol("OpenContext", tag, currentIndentation + extraIndent)); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterParenContext.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterParenContext.java new file mode 100644 index 000000000..18d28fb65 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionEnterParenContext.java @@ -0,0 +1,31 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * An action for a state transition. + * Calls to apply will add a new context element to the stack. This context element will increase + * indentation by an amount based on the current tape location. + */ +public class ActionEnterParenContext implements Action { + private int tag; + + /** + * @param tag Tag to use to identify the new context + */ + public ActionEnterParenContext(int tag) { + this.tag = tag; + } + + /** + * Apply transition logic to the input tape and stack. + * In this case, push an additional context element onto the stack. + * + * @param input The current input tape + * @param stack The current context + */ + public void apply(InputTape input, ContextStack stack) { + int line = input.getLinePosition(); + int current = input.getPosition(); + int currentIndentation = current - line; + stack.push(new ContextStack.ContextSymbol("OpenContext", tag, currentIndentation)); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionLeaveContext.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionLeaveContext.java new file mode 100644 index 000000000..09f24e5db --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionLeaveContext.java @@ -0,0 +1,30 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * An action for a state transition. + * Calls to apply will pop a specific context element from the stack. + */ +public class ActionLeaveContext implements Action { + private int tag; + + /** + * @param tag Tag to use to identify the context to remove. + */ + public ActionLeaveContext(int tag) { + this.tag = tag; + } + + /** + * Apply transition logic to the input tape and stack. + * In this case, pop an existings context element from the stack. + * + * @param input The current input tape + * @param stack The current context + */ + public void apply(InputTape input, ContextStack stack) { + if(stack.isEmpty()) { + throw new RuntimeException("Error: Attempting to pop from empty stack"); + } + stack.pop(); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionRead.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionRead.java new file mode 100644 index 000000000..e9f5b1a72 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ActionRead.java @@ -0,0 +1,38 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * An action for a state transition. + * Calls to apply will advance the input tape by a fixed amount. + */ +public class ActionRead implements Action { + private int size; + + /** + * @param substring Expected string to read. + */ + public ActionRead(String substring) { + this.size = substring.length(); + } + + /** + * @param size Number of character to read. + */ + public ActionRead(int size) { + this.size = size; + } + + /** + * Apply transition logic to the input tape and stack. + * In this case, read a fixed number of characters from the tape. + * + * @param input The current input tape + * @param stack The current context + */ + public void apply(InputTape input, ContextStack stack) { + if(input.atEnd()) { + throw new RuntimeException("Error: Attempting to read from empty tape"); + } + + input.step(this.size); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ContextStack.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ContextStack.java new file mode 100644 index 000000000..4e1774459 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/ContextStack.java @@ -0,0 +1,95 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import java.util.ArrayList; + +/** + * A class representing a stack. Each element on the stack is tagged with a context id, along with an associated + * indentation level. The stack represents a context for a given line of code; indentation for that line can be + * computed using only the top element of the stack. + */ +public class ContextStack +{ + private ArrayList base; + + public ContextStack() { + this.base = new ArrayList(); + } + + public ContextStack(ContextStack toCopy) { + this.base = new ArrayList(toCopy.base); + } + + /** + * Push an additional element to the end of the stack. + * @param symbol The context to push + */ + public void push(ContextSymbol symbol) { + this.base.add(symbol); + } + + /** + * Check is the stack is empty. + * @return true if the stack has no elements. + */ + public boolean isEmpty() { + return this.base.size() == 0; + } + + /** + * Pop and return the last element of the stack. + * @return the top context on the stack (or null if the stack is empty) + */ + public ContextSymbol pop() { + if(this.isEmpty()) + return null; + return this.base.remove(this.base.size() - 1); + } + + /** + * Return the last element of the stack. + * @return the top context on the stack (or null if the stack is empty) + */ + public ContextSymbol peek() { + if(this.isEmpty()) + return null; + return this.base.get(this.base.size() - 1); + } + + /** + * Compute the indentation level corresponding to the stack. This is the indentation level of the top element. + * @return the associated indentation level of this stack. + */ + public int getIndentationLevel() { + if(this.isEmpty()) + return 0; + return this.peek().indentation; + } + + /** + * An element on the stack. A specific type of context can be identified using its tag. + */ + public static class ContextSymbol + { + private String name; + private int tag; + private int indentation; + + /** + * @param name The name of the context + * @param tag The identifying tag for the context + * @param indentation The indentation level for the context. + */ + public ContextSymbol(String name, int tag, int indentation) { + this.name = name; + this.tag = tag; + this.indentation = indentation; + } + + /** + * @return the identifying tag of the context. + */ + public int getTag() { + return tag; + } + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Indenter.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Indenter.java new file mode 100644 index 000000000..40615f526 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Indenter.java @@ -0,0 +1,292 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import javax.swing.text.*; +import edu.rice.cs.drjava.model.AbstractDJDocument; +/** + * A class which can indent a single line of an AbstractDJDocument at a time. + * + * TODO: Cache indendation computation on lines; when indenting many lines sequentially, + * most of the computation can be reused. + */ +public class Indenter +{ + /** Enumeration of reasons why indentation may be preformed. */ + public enum IndentReason { + /** Indicates that an enter key press caused the indentation. This is important for some rules dealing with stars + * at the line start in multiline comments + */ + ENTER_KEY_PRESS, + /** Indicates that indentation was started for some other reason. This is important for some rules dealing with stars + * at the line start in multiline comments + */ + OTHER + } + + private PushDownAutomata machine; + private StateSymbol startingState; + + private static int SQUIGGLY = 0; + private static int PAREN = 1; + private static int SQUARE = 2; + + private static int ARRAY = 3; + private static int COMMENT_TAG = 4; + + private static int ANNOTATION = 5; + private static int IN_CASE = 6; + + /** + * Construct a new indentation engine. + * + * @param indentLevel The number of spaces to use for a tab. + */ + public Indenter(int indentLevel) { + this.build(indentLevel); + } + + public void build(int indentLevel) { + StateSymbol start = new StateSymbol("start_statement"); + StateSymbol normal = new StateSymbol("inside_statement", indentLevel); + StateSymbol end = new StateSymbol("end_statement"); + + StateSymbol openingbrace = new StateSymbol("opening_brace"); + StateSymbol equals = new StateSymbol("after_equals"); + StateSymbol colon = new StateSymbol("after_colon"); + + + setupCommentStates(start, "start"); + start.addTransition(new StateTransition(start, new TriggerSubstring(" "), new ActionRead(1))); + start.addTransition(new StateTransition(start, new TriggerSubstring("\n"), new ActionRead(1))); + start.addTransition(new StateTransition(normal, new TriggerChar('@'), new ActionRead(1), new ActionEnterContext(ANNOTATION, 0))); + start.addTransition(new StateTransition(normal, Trigger.ALWAYS)); + + setupCommentStates(end, "end"); + end.addTransition(new StateTransition(end, new TriggerSubstring(" "), new ActionRead(1))); + end.addTransition(new StateTransition(start, new TriggerSubstring("\n"), new ActionRead("\n"))); + end.addTransition(new StateTransition(start, Trigger.ALWAYS)); + + setupCommentStates(normal, "normal"); + setupStringStates(normal, "normal"); + setupBraceTransitions(start, normal, end, '(', ')', PAREN); + setupBraceTransitions(start, normal, end, '[', ']', SQUARE); + + normal.addTransition(new StateTransition(end, new TriggerSubstring(";"), new ActionRead(";"))); + normal.addTransition(new StateTransition(end, new Trigger[]{new TriggerSubstring("\n"), new TriggerContext(ANNOTATION)}, new Action[]{new ActionLeaveContext(ANNOTATION)})); + + //Detect { and }. Note { might open a new code context OR open an array, while } can close both + normal.addTransition(new StateTransition(normal, new Trigger[]{new TriggerChar('{'), new TriggerContext(ARRAY)}, new Action[]{new ActionRead(1), new ActionEnterContext(ARRAY, indentLevel)})); + normal.addTransition(new StateTransition(openingbrace, new TriggerChar('{'), new ActionRead(1))); + normal.addTransition(new StateTransition(normal, new Trigger[]{new TriggerChar('}'), new TriggerContext(IN_CASE)}, new Action[]{new ActionLeaveContext(IN_CASE)})); + normal.addTransition(new StateTransition(end, new Trigger[]{new TriggerChar('}'), new TriggerContext(SQUIGGLY)}, new Action[]{new ActionRead(1), new ActionLeaveContext(SQUIGGLY)})); + normal.addTransition(new StateTransition(end, new Trigger[]{new TriggerChar('}'), new TriggerContext(ARRAY)}, new Action[]{new ActionRead(1), new ActionLeaveContext(ARRAY)})); + + //Determine if ':' ends the line + normal.addTransition(new StateTransition(colon, new TriggerChar(':'), new ActionRead(1))); + + setupCommentStates(colon, "after_color"); + //If we are already directly in a case context, there is no need to enter another one. + colon.addTransition(new StateTransition(end, new TriggerContext(IN_CASE))); + colon.addTransition(new StateTransition(equals, new TriggerChar(' '), new ActionRead(1))); + colon.addTransition(new StateTransition(start, new TriggerChar('\n'), new ActionRead(1), new ActionEnterContext(IN_CASE, indentLevel))); + colon.addTransition(new StateTransition(normal, Trigger.ALWAYS)); + + //={ opens an array (with any amount of spaces inbetween + normal.addTransition(new StateTransition(equals, new TriggerChar('='), new ActionRead(1))); + equals.addTransition(new StateTransition(equals, new TriggerChar(' '), new ActionRead(1))); + equals.addTransition(new StateTransition(normal, new TriggerChar('{'), new ActionRead(1), new ActionEnterContext(ARRAY, 0))); + equals.addTransition(new StateTransition(normal, Trigger.ALWAYS)); + + //If there is text on the line opening a { context, it counts as an array initialization + openingbrace.addTransition(new StateTransition(openingbrace, new TriggerChar(' '), new ActionRead(1))); + + openingbrace.addTransition(new StateTransition(openingbrace, new Trigger[]{new TriggerChar('\n'), new TriggerContext(PAREN)}, new Action[]{new ActionLeaveContext(PAREN)})); + openingbrace.addTransition(new StateTransition(openingbrace, new Trigger[]{new TriggerChar('\n'), new TriggerContext(SQUARE)}, new Action[]{new ActionLeaveContext(SQUARE)})); + + openingbrace.addTransition(new StateTransition(start, new TriggerChar('\n'), new ActionRead(1), new ActionEnterContext(SQUIGGLY, indentLevel))); + openingbrace.addTransition(new StateTransition(start, Trigger.ALWAYS, new ActionEnterContext(ARRAY, 0))); + + //Ignore all other characters + normal.addTransition(new StateTransition(normal, Trigger.ALWAYS, new ActionRead(1))); + + this.startingState = start; + this.machine = new PushDownAutomata(this.startingState); + } + + private void setupBraceTransitions(StateSymbol start, StateSymbol inside, StateSymbol end, char open, char close, int contextId) { + inside.addTransition(new StateTransition(start, new TriggerChar(open), new ActionEnterParenContext(contextId), new ActionRead(1))); + inside.addTransition(new StateTransition(inside, new Trigger[]{new TriggerChar(close), new TriggerContext(contextId)}, new Action[]{new ActionRead(1), new ActionLeaveContext(contextId)})); + + Trigger onOperation = new TriggerAnyChar(',', '+', '-', '*', '/', '&', '%', '|'); + inside.addTransition(new StateTransition(end, new Trigger[]{new TriggerContext(contextId), onOperation}, new Action[]{new ActionRead(1)})); + } + + private void setupCommentStates(StateSymbol base, String name) { + StateSymbol linecomment = new StateSymbol(name + "_linecomment"); + StateSymbol blockcomment = new StateSymbol(name + "_blockcomment", 1); + + base.addTransition(new StateTransition(linecomment, new TriggerSubstring("//"), new ActionRead(2))); + base.addTransition(new StateTransition(blockcomment, new TriggerSubstring("/*"), new ActionRead(2), new ActionEnterContext(COMMENT_TAG, 0))); + + linecomment.addTransition(new StateTransition(base, new TriggerSubstring("\n"))); //exit the comment on newline + linecomment.addTransition(new StateTransition(linecomment, Trigger.ALWAYS, new ActionRead(1))); + + blockcomment.addTransition(new StateTransition(base, new TriggerSubstring("*/"), new ActionRead(2), new ActionLeaveContext(COMMENT_TAG))); + blockcomment.addTransition(new StateTransition(blockcomment, Trigger.ALWAYS, new ActionRead(1))); + } + + private void setupStringStates(StateSymbol base, String name) { + StateSymbol instring = new StateSymbol(name + "_string"); + StateSymbol inchar = new StateSymbol(name + "_char"); + + base.addTransition(new StateTransition(instring, new TriggerSubstring("\""), new ActionRead(1))); + base.addTransition(new StateTransition(inchar, new TriggerSubstring("'"), new ActionRead(1))); + + instring.addTransition(new StateTransition(instring, new TriggerSubstring("\\\\"), new ActionRead(2))); + instring.addTransition(new StateTransition(instring, new TriggerSubstring("\\\""), new ActionRead(2))); //Handle escaped double-quote + instring.addTransition(new StateTransition(base, new TriggerSubstring("\""), new ActionRead(1))); + instring.addTransition(new StateTransition(base, new TriggerSubstring("\n"))); //exit the string on newline + instring.addTransition(new StateTransition(instring, Trigger.ALWAYS, new ActionRead(1))); + + inchar.addTransition(new StateTransition(inchar, new TriggerSubstring("\\\\"), new ActionRead(2))); + inchar.addTransition(new StateTransition(inchar, new TriggerSubstring("\\\'"), new ActionRead(2))); //Handle escaped quote + inchar.addTransition(new StateTransition(base, new TriggerSubstring("'"), new ActionRead(1))); + inchar.addTransition(new StateTransition(base, new TriggerSubstring("\n"))); //exit the char on newline + inchar.addTransition(new StateTransition(inchar, Trigger.ALWAYS, new ActionRead(1))); + } + + private static String getFirstChar(AbstractDJDocument doc, boolean acceptComments) { + int here = doc.getCurrentLocation(); + + try { + int loc = doc.getFirstNonWSCharPos(here, acceptComments); + if(loc != -1 && loc <= doc._getLineEndPos(here)) + return doc.getText(loc, 1); + } catch(BadLocationException e) { + //fallthrough + } + return ""; + } + + private static String getLastChar(AbstractDJDocument doc) { + int here = doc.getCurrentLocation(); + int end = doc._getLineEndPos(here); + + try { + int loc = doc._findPrevNonWSCharPos(end); + if(loc != -1 && loc <= doc._getLineEndPos(here)) + return doc.getText(loc, 1); + } catch(BadLocationException e) { + //fallthrough + } + return ""; + } + + /** + * Indent a single line of an AbstractDJDocument. + * @param doc The document to indent. The current line is specified by the document position. + * @param stack The context state of the current line. + @param currentState The state of the current document location. + */ + private void indentLine(AbstractDJDocument doc, ContextStack stack, StateSymbol currentState, Indenter.IndentReason reason) { + int here = doc.getCurrentLocation(); + int end = doc._getLineEndPos(here); + + //Special case: If // is fully left-justified, do not indent the line + try { + if(doc.getText(here, 2).equals("//")) { + return; + } + }catch(BadLocationException e) { + + } + + String first = getFirstChar(doc, false); + String firstReal = getFirstChar(doc, true); + + if(reason == Indenter.IndentReason.ENTER_KEY_PRESS || here == end) { + //Special case: If a block comment line does not already include a *, add it. + if(!firstReal.equals("*") && !stack.isEmpty() && stack.peek().getTag() == COMMENT_TAG) { + doc.setTab("* ", here); + } + } + + //Special case: } is not included in its indentation level. + if(first.equals("}") && !stack.isEmpty()) { + if(stack.peek().getTag() == IN_CASE) + stack.pop(); + + if(!stack.isEmpty() && (stack.peek().getTag() == SQUIGGLY || stack.peek().getTag() == ARRAY)) + stack.pop(); + } + + //Special case: a line ending in : is unindented + if(!stack.isEmpty() && stack.peek().getTag() == IN_CASE) { + if(getLastChar(doc).equals(":")) + stack.pop(); + } + + int indentation = stack.getIndentationLevel() + currentState.getIndentationBase(); + + //Special case: Do not include line-continue indentation with { and } + if(first.equals("{") || first.equals("}")) { + if(stack.isEmpty() || stack.peek().getTag() != ARRAY) + indentation -= currentState.getIndentationBase(); + } + + doc.setTab(indentation, here); + } + + /** + * Indent a single line of an AbstractDJDocument. + * @param doc The document to indent. The current line is specified by the document position. + * @param reason The reason this line is being indented + * @returns The running state at the end of the computation, to be returned if the next line is indented. + */ + public RunningState indent(AbstractDJDocument doc, Indenter.IndentReason reason) { + return indent(doc, reason, new RunningState()); + } + + /** + * Indent a single line of an AbstractDJDocument. + * @param doc The document to indent. The current line is specified by the document position. + * @param reason The reason this line is being indented + * @param cache The previous running state on this document + * @returns The running state at the end of the computation, to be returned if the next line is indented. + */ + public RunningState indent(AbstractDJDocument doc, Indenter.IndentReason reason, RunningState cache) { + int here = doc.getCurrentLocation(); + int startLine = doc._getLineStartPos(here); + + if(cache == null || cache.position > here) { + cache = new RunningState(); + } + + String text = doc.getText(); + machine.start(new InputTape(text.substring(cache.position, startLine)), cache.stack, cache.state); + while(!machine.getInput().atEnd()) + machine.advance(); + + indentLine(doc, new ContextStack(machine.getStack()), machine.getCurrentState(), reason); + + return new RunningState(doc.getCurrentLocation(), machine); + } + + public class RunningState + { + private final int position; + private final ContextStack stack; + private final StateSymbol state; + + public RunningState() { + this.position = 0; + this.stack = new ContextStack(); + this.state = startingState; + } + + public RunningState(int fixedPosition, PushDownAutomata machine) { + this.position = fixedPosition; + this.stack = machine.getStack(); + this.state = machine.getCurrentState(); + } + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTape.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTape.java new file mode 100644 index 000000000..36e1429dd --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTape.java @@ -0,0 +1,102 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A class representing an input tape. It reads a string from left to right, allowing reads only from the head. + */ +public class InputTape +{ + private String content; + private int position; + private int lineStart; + + /** + * Create a new tape which reads the specified string from left to right. + * + * @param Contents to use for the tape. + */ + public InputTape(String content) { + this.content = content; + this.position = 0; + this.lineStart = 0; + } + + /** + * @return The character currently selected by the tape. + */ + public char peek() { + return this.content.charAt(position); + } + + /** + * Determine if the future characters on a tape matches a specified substring. + * @param substring The string to search for + * @return true if the input matches the contents of the tape at the head. + */ + public boolean nextMatches(String substring) { + if((substring.length() + position) > content.length()) + return false; + + return content.substring(position, position + substring.length()).equals(substring); + } + + /** + * Determine if the future character on a tape matches a specified character. + * @param check The character to check for + * @return true if the input matches the contents of the tape at the head. + */ + public boolean nextMatches(char check) { + if(position >= content.length()) + return false; + + return content.charAt(position) == check; + } + + /** + * @param length The number of steps to advance the head. + */ + public void step(int length) { + this.position += length; + if(this.position > this.content.length()) + this.position = this.content.length(); + } + + /** + * Advance the head by a single step. + */ + public void step() { + step(1); + } + + /** + * @return true if the tape has read the entire base string. + */ + public boolean atEnd() { + return this.content.length() == this.position; + } + + /** + * @return the current position of the tape head in the string. + */ + public int getPosition() { + return position; + } + + /** + * @return the most recent new line in the string. + * //TODO: Cache this more often? For now, it is only called when the PDA encounters a ( + */ + public int getLinePosition() { + return this.content.lastIndexOf("\n", position); + } + + /** + * Represent a tape as a string for the sake of debugging. + */ + public String toString() { + if(this.content.length() > 10) { + return "$InputTape[" + content.substring(0, 10) + "..., " + position + "]"; + } else { + return "$InputTape[" + content + ", " + position + "]"; + } + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTapeTest.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTapeTest.java new file mode 100644 index 000000000..c6e708a2e --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/InputTapeTest.java @@ -0,0 +1,39 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import junit.framework.TestCase; + +/** + * Simple functional test of the Input Tape. + */ +public class InputTapeTest extends TestCase +{ + /** + * A simple test to ensure InputTape reads in the correct order. + */ + public void testInputTape() { + String text = "3.14159"; + InputTape tape = new InputTape(text); + assert(!tape.atEnd()); + assert(tape.peek() == '3'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '.'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '1'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '4'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '1'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '5'); + tape.step(); + assert(!tape.atEnd()); + assert(tape.peek() == '9'); + tape.step(); + assert(tape.atEnd()); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomata.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomata.java new file mode 100644 index 000000000..24d3bd392 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomata.java @@ -0,0 +1,75 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import java.util.ArrayList; +import java.lang.Comparable; + +/** + * A push down automata. This is a state machine with an input tape and associated stack. + * This machine reads the input tape from left to right, changing states and modifying the stack + * as appropriate. + */ +public class PushDownAutomata +{ + private StateSymbol startingState; + + private StateSymbol currentState; + private ContextStack stack; + private InputTape input; + + /** + * @param The starting state for this PDA. + */ + public PushDownAutomata(StateSymbol startingState) { + this.startingState = startingState; + } + + /** + * Restart the machine. Begin running the machine on the provided input. + * @param input Input tape to process. + */ + public void start(InputTape input) { + this.stack = new ContextStack(); + this.currentState = startingState; + this.input = input; + } + + /** + * Restart the machine. Begin running the machine on the provided input, stack, and state. + * @param input Input tape to process. + * @param inputStack Stack to begin with + * @param inputState State to begin on + */ + public void start(InputTape input, ContextStack inputStack, StateSymbol inputState) { + this.stack = inputStack; + this.currentState = inputState; + this.input = input; + } + + /** + * Advance the machine by a single transition. + */ + public void advance() { + currentState = currentState.doTransition(input, stack); + } + + /** + * @return the underlying input tape. + */ + protected InputTape getInput() { + return input; + } + + /** + * @return the underlying current state. + */ + protected StateSymbol getCurrentState() { + return currentState; + } + + /** + * @return the underlying stack. + */ + protected ContextStack getStack() { + return stack; + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomataTest.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomataTest.java new file mode 100644 index 000000000..988c3de13 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/PushDownAutomataTest.java @@ -0,0 +1,76 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import junit.framework.TestCase; + +/** + * Run several simple functional tests on PushDownAutomata + */ +public class PushDownAutomataTest extends TestCase +{ + /** + * Generate a simple transition between states. + */ + private static StateTransition simpleTrans(StateSymbol dest, String trigger) { + return new StateTransition(dest, new TriggerSubstring(trigger), new ActionRead(trigger)); + } + + /** + * Test the state transitions of a PDA. + */ + public void testMachineBasic() { + StateSymbol s0 = new StateSymbol("0A"); + StateSymbol s1 = new StateSymbol("1A"); + StateSymbol s2 = new StateSymbol("2A"); + + s0.addTransition(simpleTrans(s1, "A")); + s0.addTransition(simpleTrans(s0, "B")); + s1.addTransition(simpleTrans(s2, "A")); + s1.addTransition(simpleTrans(s0, "B")); + s2.addTransition(simpleTrans(s2, "A")); + s2.addTransition(simpleTrans(s0, "B")); + + PushDownAutomata machine = new PushDownAutomata(s0); + + machine.start(new InputTape("AAABA")); + assert(machine.getCurrentState() == s0); + machine.advance(); + assert(machine.getCurrentState() == s1); + machine.advance(); + assert(machine.getCurrentState() == s2); + machine.advance(); + assert(machine.getCurrentState() == s2); + machine.advance(); + assert(machine.getCurrentState() == s0); + machine.advance(); + assert(machine.getCurrentState() == s1); + } + + /** + * Test the stack transitions of a PDA. + */ + public void testMachineContext() { + StateSymbol s = new StateSymbol("state"); + + s.addTransition(new StateTransition(s, new TriggerSubstring("("), new ActionRead("("), new ActionEnterContext(0, 2))); + + + s.addTransition(new StateTransition(s, new Trigger[]{new TriggerSubstring(")")}, new Action[]{new ActionRead(")"), new ActionLeaveContext(0)})); + + PushDownAutomata machine = new PushDownAutomata(s); + + machine.start(new InputTape("(()())")); + assert(machine.getStack().getIndentationLevel() == 0); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 2); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 4); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 2); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 4); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 2); + machine.advance(); + assert(machine.getStack().getIndentationLevel() == 0); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateSymbol.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateSymbol.java new file mode 100644 index 000000000..e2783e821 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateSymbol.java @@ -0,0 +1,72 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +import java.util.ArrayList; + +/** + * A state in the state machine. + * Each state has a series of possible transitions. On transition, it looks at all + * applicable transitions and selects one to apply. + */ +public class StateSymbol +{ + private String name; + private ArrayList transitions; + private int indent; + + /** + * @param name An identifying name for the state. + */ + public StateSymbol(String name) { + this(name, 0); + } + + /** + * @param name An identifying name for the state. + * @param indent The indentation level when in this state + */ + public StateSymbol(String name, int indent) { + this.name = name; + this.indent = indent; + this.transitions = new ArrayList(); + } + + /** + * Add a new transition for a state. + * @param transition The new transition to add. + */ + public void addTransition(StateTransition transisiton) { + this.transitions.add(transisiton); + } + + /** + * Follow a single transition from this state. This is likely to modify the input tape or the stack. + * Each transition on the state is checked and the first applicable transition is applied. Transitions are checked + * in the order they were added. + * @param input The current input tape. + * @param stack The current context + * @return a new current state after applying a transition. + */ + public StateSymbol doTransition(InputTape input, ContextStack stack) { + for(StateTransition transition : transitions) { + if(transition.canApply(input, stack)) + return transition.apply(input, stack); + } + + throw new RuntimeException("Unable to find valid transition from " + this.toString() + " with next character " + input.peek()); + } + + /** + * The current state may contribute the indentation level. + * @return The contribution to the indentation level by this state. + */ + public int getIndentationBase() { + return indent; + } + + /** + * Represent a state as a string for the sake of debugging. + */ + public String toString() { + return "$State[" + name + "]"; + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateTransition.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateTransition.java new file mode 100644 index 000000000..bb6210c27 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/StateTransition.java @@ -0,0 +1,60 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A transition between two state machine states. + * Each transition will examine the tape and stack to determine when to fire. + * When the proper conditions are satisfied, a transition can be applied + * to (maybe) change the state, input tape, and stack. + */ +public class StateTransition { + private StateSymbol dest; + private Trigger[] triggers; + private Action[] actions; + + /** + * @param dest The new state after applying this transition. + * @param triggers All conditions which must be satisfied for this transition. + * @param actions All actions which occur when this transition occurs. + */ + public StateTransition(StateSymbol dest, Trigger[] triggers, Action[] actions) { + this.triggers = triggers; + this.actions = actions; + this.dest = dest; + } + + /** + * @param dest The new state after applying this transition. + * @param trigger The condition which must be satisfied for this transition. + * @param actions All actions which occur when this transition occurs. + */ + public StateTransition(StateSymbol dest, Trigger trigger, Action... actions) { + this(dest, new Trigger[]{trigger}, actions); + } + + /** + * Apply transition logic to the input tape and stack. + * This is likely to modify the input tape or the stack. + * @param input The current input tape + * @param stack The current context + */ + public StateSymbol apply(InputTape input, ContextStack stack) { + for(Action action : actions) + action.apply(input, stack); + return dest; + } + + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack) { + for(Trigger trigger : triggers) { + if(!trigger.canApply(input, stack)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Trigger.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Trigger.java new file mode 100644 index 000000000..ea33473d2 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/Trigger.java @@ -0,0 +1,23 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A trigger for a state transition. + * Calls to canApply on a Trigger will decide based on input and stack context + * if the transition logic holds. + */ +public interface Trigger +{ + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack); + + public static Trigger ALWAYS = new Trigger() { + public boolean canApply(InputTape input, ContextStack stack) { + return true; + } + }; +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerAnyChar.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerAnyChar.java new file mode 100644 index 000000000..a588b8e9e --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerAnyChar.java @@ -0,0 +1,37 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A trigger for a state transition. + * Calls to canApply will decide if the current input matches any of several expected chars. + */ +public class TriggerAnyChar implements Trigger +{ + private char[] chars; + + /** + * @param chars An array of possible expected input values + */ + public TriggerAnyChar(char... chars) { + this.chars = chars; + } + + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * + * In this case, check if the tape contains any of several characters. + * + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack) { + if(input.atEnd()) + return false; + + for(char c : chars) { + if(input.nextMatches(c)) + return true; + } + return false; + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerChar.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerChar.java new file mode 100644 index 000000000..7d8884378 --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerChar.java @@ -0,0 +1,33 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A trigger for a state transition. + * Calls to canApply will decide if the current input matches the given char. + */ +public class TriggerChar implements Trigger +{ + private char check; + + /** + * @param check The expected next character + */ + public TriggerChar(char check) { + this.check = check; + } + + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * + * In this case, check if the tape contains a certain char. + * + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack) { + if(input.atEnd()) + return false; + + return input.nextMatches(check); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerContext.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerContext.java new file mode 100644 index 000000000..83486d81b --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerContext.java @@ -0,0 +1,34 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A trigger for a state transition. + * Calls to canApply will decide if the stack is in the proper context. + */ +public class TriggerContext implements Trigger +{ + private int tag; + + /** + * @param tag The context identifier to check for. + */ + public TriggerContext(int tag) { + this.tag = tag; + } + + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * + * In this case, check if we are currently in a specific context. + * + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack) { + if(stack.isEmpty()) + return false; + + ContextStack.ContextSymbol top = stack.peek(); + return top.getTag() == tag; + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerSubstring.java b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerSubstring.java new file mode 100644 index 000000000..5a373caad --- /dev/null +++ b/drjava/src/edu/rice/cs/drjava/model/definitions/indent/statemachine/TriggerSubstring.java @@ -0,0 +1,33 @@ +package edu.rice.cs.drjava.model.definitions.indent.statemachine; + +/** + * A trigger for a state transition. + * Calls to canApply will decide if the current input matches some expected value. + */ +public class TriggerSubstring implements Trigger +{ + private String substring; + + /** + * @param substring a string to check for on the tape. + */ + public TriggerSubstring(String substring) { + this.substring = substring; + } + + /** + * Determine if the transition can apply to the input tape and stack. + * This will not modify the input tape or the stack. + * + * In this case, check if the tape contains a certain string. + * + * @param input The current input tape + * @param stack The current context + */ + public boolean canApply(InputTape input, ContextStack stack) { + if(input.atEnd()) + return false; + + return input.nextMatches(substring); + } +} \ No newline at end of file diff --git a/drjava/src/edu/rice/cs/drjava/ui/AbstractDJPane.java b/drjava/src/edu/rice/cs/drjava/ui/AbstractDJPane.java index cec994eae..fc493aa8a 100644 --- a/drjava/src/edu/rice/cs/drjava/ui/AbstractDJPane.java +++ b/drjava/src/edu/rice/cs/drjava/ui/AbstractDJPane.java @@ -39,7 +39,7 @@ import edu.rice.cs.drjava.DrJava; import edu.rice.cs.drjava.config.*; import edu.rice.cs.drjava.model.*; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.util.swing.*; import edu.rice.cs.util.swing.Utilities; diff --git a/drjava/src/edu/rice/cs/drjava/ui/DefinitionsPane.java b/drjava/src/edu/rice/cs/drjava/ui/DefinitionsPane.java index bcc212c76..ecfc34fbd 100644 --- a/drjava/src/edu/rice/cs/drjava/ui/DefinitionsPane.java +++ b/drjava/src/edu/rice/cs/drjava/ui/DefinitionsPane.java @@ -55,7 +55,7 @@ import edu.rice.cs.drjava.model.definitions.CompoundUndoManager; import edu.rice.cs.drjava.model.definitions.DefinitionsEditorKit; import edu.rice.cs.drjava.model.definitions.NoSuchDocumentException; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.definitions.reducedmodel.ReducedModelState; import edu.rice.cs.drjava.config.*; import edu.rice.cs.drjava.DrJava; diff --git a/drjava/src/edu/rice/cs/drjava/ui/InteractionsController.java b/drjava/src/edu/rice/cs/drjava/ui/InteractionsController.java index 8231d7ad6..4c30939e9 100644 --- a/drjava/src/edu/rice/cs/drjava/ui/InteractionsController.java +++ b/drjava/src/edu/rice/cs/drjava/ui/InteractionsController.java @@ -78,7 +78,7 @@ import edu.rice.cs.drjava.config.OptionConstants; import edu.rice.cs.drjava.config.OptionListener; import edu.rice.cs.drjava.config.OptionEvent; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.repl.InputListener; import edu.rice.cs.drjava.model.repl.InteractionsDocument; import edu.rice.cs.drjava.model.repl.InteractionsDJDocument; diff --git a/drjava/src/edu/rice/cs/drjava/ui/InteractionsPane.java b/drjava/src/edu/rice/cs/drjava/ui/InteractionsPane.java index 0383f02e4..5a08e625c 100644 --- a/drjava/src/edu/rice/cs/drjava/ui/InteractionsPane.java +++ b/drjava/src/edu/rice/cs/drjava/ui/InteractionsPane.java @@ -57,7 +57,7 @@ import edu.rice.cs.drjava.config.*; import edu.rice.cs.drjava.*; import edu.rice.cs.drjava.model.DJDocument; -import edu.rice.cs.drjava.model.definitions.indent.Indenter; +import edu.rice.cs.drjava.model.definitions.indent.statemachine.Indenter; import edu.rice.cs.drjava.model.repl.*; /** The view component for repl interaction.