package bluej.stride.framedjava.slots;

import static bluej.stride.framedjava.slots.Operator.Precedence.DOT;
import static bluej.stride.framedjava.slots.Operator.Precedence.HIGH;
import static bluej.stride.framedjava.slots.Operator.Precedence.LOW;
import static bluej.stride.framedjava.slots.Operator.Precedence.MEDIUM;
import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import bluej.stride.framedjava.slots.Operator.Precedence;

public class TestExpressionSlot
{       

   @Rule
   
   public TestRule runOnFXThreadRule = new TestRule() {        
      boolean initialised = false;
       
      @Override public Statement apply(Statement base, Description d)
{         
if (!initialised)
           {                   
            
            new JFXPanel();
               
            initialised = true;             
            }
           
         return new Statement() {                
            @Override public void evaluate() throws Throwable
{                                

            CompletableFuture<Throwable> thrown = new CompletableFuture<>();
            Platform.runLater(() -> {
                     
            try {
            base.evaluate();
            thrown.complete(null);                       
            } catch( Throwable throwable ) {
            thrown.complete(throwable);                       
            }                     
         });
      Throwable t = thrown.get();
      if (t != null)
                       
      throw t;                 
      }             
   };
}              
};


private static class CompareWrapper
{           
private String s;
       
   public CompareWrapper(String s)
{
this.s = s; 
      }
       
   @Override public boolean equals(Object o)
{
if (o instanceof CompareWrapper) return s.equals(((CompareWrapper)o).s); else{ return false;
         } 
      }
       
   @Override public String toString()
{
return s; 
      }     
   }

   
private void testInsert(String insertion, String result)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   CaretPos p = e.testingInsert(insertion, '\0');
       
   assertEquals(insertion + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));
        
       
   
   for (int i = 0; i <= insertion.length(); i++)
       {
      CaretPos caretPos = e.stringPosToCaretPos(i, false);
      if (caretPos != null)
      assertEquals("String pos -> caret pos -> string pos", i, e.caretPosToStringPos(caretPos, false));         
      }
        
       
   
   String noPos = result.replace("$", "");
       
   for (int split = 1; split < insertion.length(); split++)
       {            
      e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
      e.testingInsert(e.testingInsert(insertion.substring(0, split), '\0'),  insertion.substring(split));
           
      assertEquals(insertion + " -> " + noPos, new CompareWrapper(noPos), new CompareWrapper(e.testingGetState(null)));         
      }                       
   }
    
   
private void testMultiInsert(String multiInsertion, String firstResult, String secondResult)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   int startNest = multiInsertion.indexOf('{');
       
   int endNest = multiInsertion.indexOf('}', startNest);
   if (startNest == -1 || endNest == -1)
           
   throw new IllegalStateException();
   String before = multiInsertion.substring(0, startNest);
   String nest = multiInsertion.substring(startNest + 1, endNest);
   String after = multiInsertion.substring(endNest + 1);
        
   CaretPos p = e.testingInsert(before + "$" + after, '$');
       
   assertEquals(multiInsertion + " -> " + firstResult, new CompareWrapper(firstResult), new CompareWrapper(e.testingGetState(p)));
   p = e.testingInsert(p,  nest);
       
   assertEquals(multiInsertion + " -> " + secondResult, new CompareWrapper(secondResult), new CompareWrapper(e.testingGetState(p)));     
   }
    
   
private void testInsertExisting(String start, String insertion, String result)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   CaretPos p = e.testingInsert(start, '$');
   p = e.testingInsert(p, insertion);
        
       
   assertEquals(start + " then \"" + insertion + "\" -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));     
   }
    
    
   
private void testBackspace(String insertionInclBackspace, String result)
   {
   testBackspace(insertionInclBackspace, result, true, true);     
   }
    
   
private void testBackspace(String insertionInclBackspace, String result, boolean testBackspace, boolean testDelete)
   {
   if (testBackspace)
       {            
      InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
      CaretPos p = e.testingInsert(insertionInclBackspace, '\b');
      e.positionCaret(p);
            
      p = e.testingBackspace(p);
           
      assertEquals(insertionInclBackspace.replace("\b", "\\b") + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));         
      }
        
       
   
   int index = insertionInclBackspace.indexOf('\b');
   if (index > 0 && testDelete)
       {
      String before = insertionInclBackspace.substring(0, index);
      String after = insertionInclBackspace.substring(index + 1);
            
           
      
      after = before.substring(before.length() - 1) + after;
      before = before.substring(0, before.length() - 1);
            
           
      
      String joined = before + "\b" + after;
           
      InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
      CaretPos p = e.testingInsert(joined, '\b');
      e.positionCaret(p);
      p = e.testingDelete(p);
           
      assertEquals(joined.replace("\b", "\\DEL") + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));         
      }     
   }
    
   
private void testDeleteSelection(String src, String result)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   int startNest = src.indexOf('{');
       
   int endNest = src.indexOf('}', startNest);
   if (startNest == -1 || endNest == -1)
           
   throw new IllegalStateException();
   String before = src.substring(0, startNest);
   String nest = src.substring(startNest + 1, endNest);
   String after = src.substring(endNest + 1);
        
   CaretPos start = e.testingInsert(before, '\0');
   CaretPos end = e.testingInsert(start, nest);
   e.testingInsert(end, after);
   CaretPos p = e.testingDeleteSelection(start, end);
        
       
   assertEquals(src + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));     
   }
    
   
private void testSelectionInsert(char c, String src, String result)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   int startNest = src.indexOf('{');
       
   int endNest = src.indexOf('}', startNest);
   if (startNest == -1 || endNest == -1)
           
   throw new IllegalStateException();
   String before = src.substring(0, startNest);
   String nest = src.substring(startNest + 1, endNest);
   String after = src.substring(endNest + 1);
        
   CaretPos start = e.testingInsert(before, '\0');
   CaretPos end = e.testingInsert(start, nest);
   e.testingInsert(end, after);
   CaretPos p = e.testingInsertWithSelection(start, end, c);
        
       
   assertEquals(src + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p)));     
   }
    
   
private static class CPM
{           
private int stringPos;
       
   private CaretPos caretPos;
       
   public CPM(int stringPos, CaretPos caretPos)
       {            
      this.stringPos = stringPos;
           
      this.caretPos = caretPos;         
      }
       
   @Override
       
   public String toString()
       {            
      return "CPM [stringPos=" + stringPos + ", caretPos=" + caretPos                    
      + "]";         
      }     
   }
    
   
private void testCaretPosMap(String src, String result, CPM... cpms)
   {
   testCaretPosMap(src, result, null, cpms);     
   }
    
   
private void testCaretPosMap(String src, String result, String java, CPM... cpms)
   {        
   InfixExpression e = StructuredSlot.testingModification(token -> new InfixExpression(null, null, token));
   CaretPos p = e.testingInsert(src, '\0');
       
   assertEquals(src + " -> " + result, new CompareWrapper(result), new CompareWrapper(e.testingGetState(p).replace("$", "")));
       
   for (CPM cpm : cpms)
       {            
      assertEquals("Caret to String " + cpm.toString() + " for " + src, cpm.stringPos, e.caretPosToStringPos(cpm.caretPos, java != null));
           
      assertEquals("String to Caret " + cpm.toString() + " for " + src, cpm.caretPos, e.stringPosToCaretPos(cpm.stringPos, java != null));         
      }
        
   if (java != null)
       {
      assertEquals("To Java, from: " + src, java, e.getJavaCode());         
      }     
   }
    
   





    
   
@Test
   
public void testOperators()
   {        
   testInsert("aa", "{aa$}");
       
   testInsert("a+b", "{a}+{b$}");
       
   testInsert("a+b-c", "{a}+{b}-{c$}");
       
   testInsert("1++1", "{1}+{+1$}");
       
   testInsert("1<2&&3<=4&&5==6+8", "{1}<{2}&&{3}<={4}&&{5}=={6}+{8$}");
       
   testInsert("false!=!false", "{false}!={}!{false$}");
       
   testInsert("false!=!!!false", "{false}!={}!{}!{}!{false$}");
       
   testInsert("5==-6", "{5}=={-6$}");
       
   testInsert("5==--6", "{5}=={}-{-6$}");
       
   testInsert("5==----6", "{5}=={}-{}-{}-{-6$}");
       
   testInsert("a.b", "{a}.{b$}");
       
   testInsert("a..b", "{a}..{b$}");
       
   testInsert("y-1", "{y}-{1$}");
       
   testInsert("getY()*1", "{getY}_({})_{}*{1$}");
       
   testInsert("getY()-1", "{getY}_({})_{}-{1$}");
       
   testInsert("getY()+-1", "{getY}_({})_{}+{-1$}");

       
   
   testInsert("s.length()..10", "{s}.{length}_({})_{}..{10$}");
       
   
   testInsert("s.length()", "{s}.{length}_({})_{$}");
       
   testInsert("s.length().", "{s}.{length}_({})_{}.{$}");
       
   testInsert("s.length()..", "{s}.{length}_({})_{}..{$}");
       
   testInsert("s.length()..1", "{s}.{length}_({})_{}..{1$}");      
   }
    
   
@Test
   
public void testNew()
   {        
   testInsert("newton", "{newton$}");
       
   testInsert("new ton", "{}new {ton$}");     
   }
    
   
@Test
   
public void testStrings()
   {        
   
   testInsert("\"hello\"", "{}_\"hello\"_{$}");
   
   testInsert("\"hello", "{}_\"hello$\"_{}");
       
   testInsert("\"hello\"+\"world\"", "{}_\"hello\"_{}+{}_\"world\"_{$}");
       
   testInsert("\"hello\"+\"world\"+(5*6)", "{}_\"hello\"_{}+{}_\"world\"_{}+{}_({5}*{6})_{$}");
        
       
   
   testInsert("\"\\\"\"", "{}_\"\\\"\"_{$}");
       
   
   testInsert("\"\\\'\"", "{}_\"\\\'\"_{$}");
       
   
   testInsert("\"'\"", "{}_\"'\"_{$}");
        
       
   
   testMultiInsert("abc{\"}def", "{abc$def}", "{abc}_\"$\"_{def}");
       
   testMultiInsert("abc{\"}", "{abc$}", "{abc}_\"$\"_{}");
       
   testMultiInsert("{\"}def", "{$def}", "{}_\"$\"_{def}");
       
   testMultiInsert("abc{\"}.def", "{abc$}.{def}", "{abc}_\"$\"_{}.{def}");
       
   testMultiInsert("abc{\"}*def", "{abc$}*{def}", "{abc}_\"$\"_{}*{def}");
       
   testMultiInsert("abc{\"}def()", "{abc$def}_({})_{}", "{abc}_\"$\"_{def}_({})_{}");
       
   testMultiInsert("abc{\"}()", "{abc$}_({})_{}", "{abc}_\"$\"_{}_({})_{}");

       
   
   
   testInsertExisting("$\"b\"", "\"a", "{}_\"a$\"_{}_\"b\"_{}");
       
   testInsertExisting("$\"b\"", "\"a\"", "{}_\"a\"_{$}_\"b\"_{}");
       
   
   testInsertExisting("\"a\"$", "\"b", "{}_\"a\"_{}_\"b$\"_{}");

       
   
   testInsert("'a'", "{}_'a'_{$}");
       
   testInsert("'a", "{}_'a$'_{}");
       
   
   testInsert("'\\''", "{}_'\\''_{$}");
       
   
   testInsert("'\\\"'", "{}_'\\\"'_{$}");
       
   
   testInsert("'\"'", "{}_'\"'_{$}");
        
        
   
   testInsert("c == '\\\\' || c == '\"' || c == '\\''",
           
   "{c}=={}_\'\\\\\'_{}||{c}=={}_'\"'_{}||{c}=={}_'\\''_{$}");

       
   
   testBackspace("\"a\bb\"", "{}_\"$b\"_{}");
       
   testBackspace("\"\bab\"", "{$ab}");
       
   testBackspace("\"ab\b\"", "{}_\"a$\"_{}");
       
   testBackspace("\"ab\"\b", "{ab$}");     
   }
    
   
@Test
   
public void testBrackets()
   {        
   testInsert("a+(b-c)", "{a}+{}_({b}-{c})_{$}");
       
   testInsert("a+(b-(c*d))", "{a}+{}_({b}-{}_({c}*{d})_{})_{$}");
        
       
   
   testInsert("(a+b", "{}_({a}+{b$})_{}");
        
       
   testInsert("(((", "{}_({}_({}_({$})_{})_{})_{}");
       
   testInsert("((()", "{}_({}_({}_({})_{$})_{})_{}");
       
   testInsert("((())", "{}_({}_({}_({})_{})_{$})_{}");
       
   testInsert("((()))", "{}_({}_({}_({})_{})_{})_{$}");
        
       
   testInsert("(a+(b*c)+d)", "{}_({a}+{}_({b}*{c})_{}+{d})_{$}");
        
       
   testMultiInsert("({(MyWorld)}getWorld()).getWidth()",
               
   "{}_({$getWorld}_({})_{})_{}.{getWidth}_({})_{}",
               
   "{}_({}_({MyWorld})_{$getWorld}_({})_{})_{}.{getWidth}_({})_{}");
        
       
   testInsert("a(bc)d", "{a}_({bc})_{d$}");     
   }
    
   
@Test
   
public void testBackspace()
   {        
   testBackspace("\bxyz", "{$xyz}");
       
   testBackspace("x\byz", "{$yz}");
       
   testBackspace("xy\bz", "{x$z}");
       
   testBackspace("xyz\b", "{xy$}");
        
       
   testBackspace("xy\b+ab", "{x$}+{ab}");
       
   testBackspace("xy+\bab", "{xy$ab}");
       
   testBackspace("xy+a\bb", "{xy}+{$b}");
        
       
   testBackspace("new t\bon", "{}new {$on}");
       
   
   testBackspace("new \bton", "{new$ton}", true, false);
       
   
   testBackspace("n\bew ton", "{$ewton}", false, true);
        
       
   testBackspace("move(\b)", "{move$}");
       
   testBackspace("(\b)", "{$}");     
   }
    
   
@Test
   
public void testFloating()
   {        
   
   
        
       
   testInsert("1.0", "{1.0$}");
       
   testInsert("10.20", "{10.20$}");
       
   testInsert("a.0", "{a}.{0$}");
       
   testInsert("1.a", "{1.a$}");
       
   testInsert("x1.a", "{x1}.{a$}");
       
   testInsert("+1", "{+1$}");
       
   testInsert("+1.0", "{+1.0$}");
       
   testInsert("+1.0e5", "{+1.0e5$}");
       
   testInsert("+1.0e", "{+1.0e$}");
       
   testInsert("+1.0e+5", "{+1.0e+5$}");
       
   testInsert("+1.0e+5+6", "{+1.0e+5}+{6$}");
       
   testInsert("+1.0p+5", "{+1.0p}+{5$}");
       
   testInsert("3+1", "{3}+{1$}");
       
   testInsert("3+1.0", "{3}+{1.0$}");
       
   testInsert("3+1.0e5", "{3}+{1.0e5$}");
       
   testInsert("3+1.0e+5", "{3}+{1.0e+5$}");
       
   testInsert("3+1.0e+5+6", "{3}+{1.0e+5}+{6$}");
       
   testInsert("3+1.0p+5", "{3}+{1.0p}+{5$}");
        
       
   testInsert("+1+2+3", "{+1}+{2}+{3$}");
       
   testInsert("+1++2", "{+1}+{+2$}");
       
   testInsert("+1++2+3", "{+1}+{+2}+{3$}");
       
   testInsert("+1++2++3", "{+1}+{+2}+{+3$}");
       
   testInsert("++1++2++3", "{}+{+1}+{+2}+{+3$}");
       
   testMultiInsert("+{1}", "{}+{$}", "{+1$}");
       
   testMultiInsert("+{+1}", "{}+{$}", "{}+{+1$}");
       
   testMultiInsert("1++{2}", "{1}+{}+{$}", "{1}+{+2$}");
        
       
   testInsert("1e6", "{1e6$}");
       
   testInsert("1e-6", "{1e-6$}");
       
   testInsert("10e20", "{10e20$}");
       
   testInsert("10e+20", "{10e+20$}");
       
   testInsert("10e-20", "{10e-20$}");
        
       
   testInsert("1.0.3", "{1.0}.{3$}");
       
   testInsert("1.0.3.4", "{1.0}.{3.4$}");
       
   testInsert("1.0.x3.4", "{1.0}.{x3}.{4$}");
        
       
   
   
   
   testBackspace("+\b1.0e-5", "{$1.0e-5}", false, true);
       
   testBackspace("+1\b.0e-5", "{}+{$}.{0e-5}", true, false);
       
   testBackspace("+1.\b0e-5", "{+1$0e-5}");
       
   testBackspace("+1.0\be-5", "{+1.$e-5}");
       
   testBackspace("+1.0e\b-5", "{+1.0$}-{5}");
       
   testBackspace("+1.0e-\b5", "{+1.0e$5}");
       
   testBackspace("+1.0e-5\b", "{+1.0e-$}");
        
       
   testMultiInsert("{1}e-6", "{$e}-{6}", "{1$e-6}");
       
   testMultiInsert("1{e}-6", "{1$}-{6}", "{1e$-6}");
       
   testMultiInsert("1e{-}6", "{1e$6}", "{1e-$6}");
       
   testMultiInsert("1e-{6}", "{1e-$}", "{1e-6$}");
        
       
   testMultiInsert("{x}1e-6", "{$1e-6}", "{x$1e}-{6}");
       
   testMultiInsert("1{x}e-6", "{1$e-6}", "{1x$e}-{6}");
       
   testMultiInsert("1e{x}-6", "{1e$-6}", "{1ex$}-{6}");
       
   testMultiInsert("1e-{x}6", "{1e-$6}", "{1e-x$6}");
        
       
   testInsert("1.0", "{1.0$}");
       
   testInsert("1..0", "{1}..{0$}");
       
   testBackspace("1..\b0", "{1.$0}", true, false); 
   testBackspace("1.\b.0", "{1$.0}", false, true); 
   testBackspace("a..\bc", "{a}.{$c}", true, false); 
   testBackspace("a.\b.c", "{a$}.{c}", false, true);      
   }
    
   
@Test
   
public void testDeleteBracket()
   {        
   testInsert("a+(b*c)", "{a}+{}_({b}*{c})_{$}");
       
   testBackspace("a+(b*c)\b", "{a}+{b}*{c$}");
       
   testBackspace("a+(\bb*c)", "{a}+{$b}*{c}");
        
   testInsert("((MyWorld)getWorld()).getWidth()",
               
   "{}_({}_({MyWorld})_{getWorld}_({})_{})_{}.{getWidth}_({})_{$}");
   testBackspace("((MyWorld)getWorld()).getWidth()\b",
               
   "{}_({}_({MyWorld})_{getWorld}_({})_{})_{}.{getWidth$}");
   testBackspace("((MyWorld)getWorld()).\bgetWidth()",
               
   "{}_({}_({MyWorld})_{getWorld}_({})_{})_{$getWidth}_({})_{}");
   testBackspace("((MyWorld)getWorld())\b.getWidth()",
               
   "{}_({MyWorld})_{getWorld}_({})_{$}.{getWidth}_({})_{}");
   testBackspace("((MyWorld)getWorld(\b)).getWidth()",
               
   "{}_({}_({MyWorld})_{getWorld$})_{}.{getWidth}_({})_{}");
   testBackspace("((MyWorld)\bgetWorld()).getWidth()",
               
   "{}_({MyWorld$getWorld}_({})_{})_{}.{getWidth}_({})_{}");
   testBackspace("((\bMyWorld)getWorld()).getWidth()",
               
   "{}_({$MyWorldgetWorld}_({})_{})_{}.{getWidth}_({})_{}");
   testBackspace("(\b(MyWorld)getWorld()).getWidth()",
               
   "{$}_({MyWorld})_{getWorld}_({})_{}.{getWidth}_({})_{}");     
   }
    
   
@Test
   
public void testDeleteSelection()
   {        
   testDeleteSelection("a{bc}d", "{a$d}");
       
   testDeleteSelection("a{bc}", "{a$}");
        
       
   testInsert("a+(b*c)-d", "{a}+{}_({b}*{c})_{}-{d$}");
       
   testDeleteSelection("{a}+(b*c)-d", "{$}+{}_({b}*{c})_{}-{d}");
       
   testDeleteSelection("{a+}(b*c)-d", "{$}_({b}*{c})_{}-{d}");
       
   testDeleteSelection("a+{(b*c)}-d", "{a}+{$}-{d}");
       
   testDeleteSelection("a{+(b*c)}-d", "{a$}-{d}");
       
   testDeleteSelection("a{+(b*c)-}d", "{a$d}");
       
   testDeleteSelection("a+({b*c})-d", "{a}+{}_({$})_{}-{d}");
        
       
   testInsert("s+\"hello\"+t", "{s}+{}_\"hello\"_{}+{t$}");
       
   testDeleteSelection("s+\"h{ell}o\"+t", "{s}+{}_\"h$o\"_{}+{t}");     
   }
    
   
@Test
   
public void testSelectionOperation()
   {        
   testSelectionInsert('(', "a{bc}d", "{a}_({bc})_{$d}");
       
   testSelectionInsert('(', "a{b+c}d", "{a}_({b}+{c})_{$d}");
       
   testSelectionInsert('(', "a{b+}d", "{a}_({b}+{})_{$d}");
       
   testSelectionInsert('(', "a{b+}", "{a}_({b}+{})_{$}");
       
   testSelectionInsert('(', "a{b++}", "{a}_({b}+{}+{})_{$}");
       
   testSelectionInsert('(', "a{b+}+", "{a}_({b}+{})_{$}+{}");
        
       
   testSelectionInsert('\"', "a{bc}d", "{a}_\"bc\"_{$d}");
       
   testSelectionInsert('\'', "a{bc}d", "{a}_\'bc\'_{$d}");
       
   testInsert("ab+cd", "{ab}+{cd$}");
       
   testSelectionInsert('\"', "a{b+c}d", "{a}_\"b+c\"_{$d}");
       
   testSelectionInsert('\'', "a{b+c}d", "{a}_\'b+c\'_{$d}");
        
       
   testInsert("a+\"hello\"+c", "{a}+{}_\"hello\"_{}+{c$}");
       
   testSelectionInsert('\"', "a+\"h{ell}o\"+c", "{a}+{}_\"hell$o\"_{}+{c}");
       
   testSelectionInsert('\"', "a+\"hello\"{+c}", "{a}+{}_\"hello\"_{}_\"+c\"_{$}");     
   }
    
   
private CaretPos makeCaretPos(int... xs)
   {        
   CaretPos p = null;
       
   for (int i = xs.length - 1; i >= 0; i--)
       {            
      p = new CaretPos(xs[i], p);         
      }
       
   return p;     
   }

   
@Test
   
public void testCaretPosMap()
   {        
   testCaretPosMap("abc", "{abc}",
               
   new CPM(0, makeCaretPos(0, 0)),
               
   new CPM(1, makeCaretPos(0, 1)),
               
   new CPM(2, makeCaretPos(0, 2))
       
   );
        
       
   testCaretPosMap("a+b+c", "{a}+{b}+{c}",
           
   new CPM(0, makeCaretPos(0, 0)),
           
   new CPM(1, makeCaretPos(0, 1)),
           
   new CPM(2, makeCaretPos(1, 0)),
           
   new CPM(3, makeCaretPos(1, 1)),
           
   new CPM(4, makeCaretPos(2, 0)),
           
   new CPM(5, makeCaretPos(2, 1))
       
   );
       
   testCaretPosMap("\"hi\"", "{}_\"hi\"_{}",
           
   new CPM(0, makeCaretPos(0, 0)),
           
   new CPM(1, makeCaretPos(1, 0)),
           
   new CPM(2, makeCaretPos(1, 1)),
           
   new CPM(3, makeCaretPos(1, 2)),
           
   new CPM(4, makeCaretPos(2, 0))
       
   );
       
   testCaretPosMap("a*(b+\"cd\"+e)-(f)", "{a}*{}_({b}+{}_\"cd\"_{}+{e})_{}-{}_({f})_{}",
           
   new CPM(0, makeCaretPos(0, 0)), 
   new CPM(1, makeCaretPos(0, 1)), 
   new CPM(2, makeCaretPos(1, 0)), 
   new CPM(3, makeCaretPos(2, 0, 0)), 
   new CPM(4, makeCaretPos(2, 0, 1)), 
   new CPM(5, makeCaretPos(2, 1, 0)), 
   new CPM(6, makeCaretPos(2, 2, 0)), 
   new CPM(7, makeCaretPos(2, 2, 1)), 
   new CPM(8, makeCaretPos(2, 2, 2)), 
   new CPM(9, makeCaretPos(2, 3, 0)), 
   new CPM(10, makeCaretPos(2, 4, 0)), 
   new CPM(11, makeCaretPos(2, 4, 1)), 
   new CPM(12, makeCaretPos(3, 0)), 
   new CPM(13, makeCaretPos(4, 0)), 
   new CPM(14, makeCaretPos(5, 0, 0)), 
   new CPM(15, makeCaretPos(5, 0, 1)), 
   new CPM(16, makeCaretPos(6, 0)) 
   );
        
       
   testCaretPosMap("gW().aO(a,b,c)", "{gW}_({})_{}.{aO}_({a},{b},{c})_{}",
               
   new CPM(0, makeCaretPos(0, 0)), 
   new CPM(1, makeCaretPos(0, 1)), 
   new CPM(2, makeCaretPos(0, 2)), 
                
               
   new CPM(3, makeCaretPos(1, 0, 0)), 
                
               
   new CPM(4, makeCaretPos(2, 0)), 
                
               
   new CPM(5, makeCaretPos(3, 0)), 
   new CPM(6, makeCaretPos(3, 1)), 
   new CPM(7, makeCaretPos(3, 2)), 
                
               
   new CPM(8, makeCaretPos(4, 0, 0)), 
   new CPM(9, makeCaretPos(4, 0, 1)), 
                
               
   new CPM(10, makeCaretPos(4, 1, 0)), 
   new CPM(11, makeCaretPos(4, 1, 1)), 
                
               
   new CPM(12, makeCaretPos(4, 2, 0)), 
   new CPM(13, makeCaretPos(4, 2, 1)), 
   new CPM(14, makeCaretPos(5, 0)) 
   );
        
       
   testCaretPosMap("1+2", "{1}+{2}", "1 + 2",
               
   new CPM(0, makeCaretPos(0, 0)),
               
   new CPM(1, makeCaretPos(0, 1)),
               
   new CPM(4, makeCaretPos(1, 0)),
               
   new CPM(5, makeCaretPos(1, 1))
       
   );
        
       
   testCaretPosMap("1++2", "{1}+{+2}", "1 + +2",
               
   new CPM(0, makeCaretPos(0, 0)),
               
   new CPM(1, makeCaretPos(0, 1)),
               
   new CPM(4, makeCaretPos(1, 0)),
               
   new CPM(5, makeCaretPos(1, 1)),
               
   new CPM(6, makeCaretPos(1, 2))
       
   );
                
       
   
   testCaretPosMap("a<:Crab", "{a}<:{Crab}",
               
   new CPM(0, makeCaretPos(0, 0)),
               
   new CPM(1, makeCaretPos(0, 1)),
               
   new CPM(3, makeCaretPos(1, 0)),
               
   new CPM(4, makeCaretPos(1, 1)),
               
   new CPM(5, makeCaretPos(1, 2)),
               
   new CPM(6, makeCaretPos(1, 3)),
               
   new CPM(7, makeCaretPos(1, 4))
       
   );
        
       
   
   testCaretPosMap("a<:Crab", "{a}<:{Crab}", "a instanceof Crab",
               
   new CPM(0, makeCaretPos(0, 0)),
               
   new CPM(1, makeCaretPos(0, 1)),
               
   new CPM(13, makeCaretPos(1, 0)),
               
   new CPM(14, makeCaretPos(1, 1)),
               
   new CPM(15, makeCaretPos(1, 2)),
               
   new CPM(16, makeCaretPos(1, 3)),
               
   new CPM(17, makeCaretPos(1, 4))
       
   );
        
   final int f = "lang.stride.Utility.makeRange".length();
       
   testCaretPosMap("1..2", "{1}..{2}", "lang.stride.Utility.makeRange(1, 2)",
               
   new CPM(f + 1, makeCaretPos(0, 0)),
               
   new CPM(f + 2, makeCaretPos(0, 1)),
               
   new CPM(f + 4, makeCaretPos(1, 0)),
               
   new CPM(f + 5, makeCaretPos(1, 1))
       
   );

       
   
   testCaretPosMap("1,2..3+4,5,6..7..8", "{1},{2}..{3}+{4},{5},{6}..{7}..{8}",
   "1, lang.stride.Utility.makeRange(2, 3 + 4), 5, lang.stride.Utility.makeRange(6, lang.stride.Utility.makeRange(7, 8))",
               
   new CPM(0, makeCaretPos(0, 0)), 
   new CPM(1, makeCaretPos(0, 1)),
                
               
   new CPM(3 + f + 1, makeCaretPos(1, 0)), 
   new CPM(3 + f + 2, makeCaretPos(1, 1)),
               
   new CPM(3 + f + 4, makeCaretPos(2, 0)), 
   new CPM(3 + f + 5, makeCaretPos(2, 1)),
               
   new CPM(3 + f + 8, makeCaretPos(3, 0)), 
   new CPM(3 + f + 9, makeCaretPos(3, 1)),
                
               
   new CPM(3 + f + 12, makeCaretPos(4, 0)), 
   new CPM(3 + f + 13, makeCaretPos(4, 1)),
                
               
   new CPM(3 + f + 15 + f + 1, makeCaretPos(5, 0)), 
   new CPM(3 + f + 15 + f + 2, makeCaretPos(5, 1)),
                
               
   new CPM(3 + f + 15 + f + 4 + f + 1, makeCaretPos(6, 0)), 
   new CPM(3 + f + 15 + f + 4 + f + 2, makeCaretPos(6, 1)),
               
   new CPM(3 + f + 15 + f + 4 + f + 4, makeCaretPos(7, 0)), 
   new CPM(3 + f + 15 + f + 4 + f + 5, makeCaretPos(7, 1))
       
   );

       
   
   testCaretPosMap("600+\"a\",40", "{600}+{}_\"a\"_{},{40}",
           
   new CPM(0, makeCaretPos(0,0)), 
   new CPM(1, makeCaretPos(0,1)),
           
   new CPM(2, makeCaretPos(0,2)),

           
   new CPM(4, makeCaretPos(1,0)), 

           
   new CPM(5, makeCaretPos(2,0)), 
   new CPM(6, makeCaretPos(2,1)), 

           
   new CPM(7, makeCaretPos(3,0)), 

           
   new CPM(8, makeCaretPos(4,0)), 
   new CPM(9, makeCaretPos(4,1))
       
   );

       
   
   testCaretPosMap("600+\"a\",40", "{600}+{}_\"a\"_{},{40}", "600 + \"a\", 40",
           
   new CPM(0, makeCaretPos(0,0)), 
   new CPM(1, makeCaretPos(0,1)),
           
   new CPM(2, makeCaretPos(0,2)),

           
   new CPM(6, makeCaretPos(1,0)), 

           
   new CPM(7, makeCaretPos(2,0)), 
   new CPM(8, makeCaretPos(2,1)), 

           
   new CPM(9, makeCaretPos(3,0)), 

           
   new CPM(11, makeCaretPos(4,0)), 
   new CPM(12, makeCaretPos(4,1))
       
   );     
   }
    
   
private void testPrecedence(String src, Precedence... precedences)
   {        
   
   src = src.replaceAll(" +", "").replaceAll("[a-zA-Z0-9]+", "x");
        
       
   ArrayList<Boolean> unary = new ArrayList<>();
       
   ArrayList<Operator> ops = new ArrayList<>();
        
       
   
   boolean lastWasOperand = false;
        
   while (src.length() > 0)
       {
      if (src.startsWith("x"))
           {                
         lastWasOperand = true;
         src = src.substring(1);             
         }
           
      
      else if (InfixExpression.isExpressionOperator(src.substring(0, 2)))
           {                
         ops.add(new Operator(src.substring(0, 2), null));
         src = src.substring(2);
         unary.add(!lastWasOperand);
               
         lastWasOperand = false;             
         }

      else if (InfixExpression.isExpressionOperator(src.substring(0, 1)))
           {                
         ops.add(new Operator(src.substring(0, 1), null));
         src = src.substring(1);
         unary.add(!lastWasOperand);
               
         lastWasOperand = false;             
         }

      else if (src.startsWith("()"))
           {
         src = src.substring(2);
         ops.add(null);
         ops.add(null);
         unary.add(false);
         unary.add(false);
               
         lastWasOperand = true;             
         }
           

      else
           {                
         throw new IllegalArgumentException("Unknown operator: " + src);             
         }         
      }
        
   if (ops.size() != precedences.length)
           
   throw new IllegalArgumentException("Incorrect number of precedences: " + ops.size() + " vs " + precedences.length);
        
   InfixExpression.calculatePrecedences(ops, unary);
        
       
   for (int i = 0; i < ops.size(); i++)
       {
      assertEquals("Operator precedence (" + (ops.get(i) == null ? "_" : ops.get(i).get()) + "), index" + i, precedences[i], ops.get(i) == null ? null : ops.get(i).getPrecedence());         
      }     
   }
    
   
@Test
   
public void testPrecedence()
   {
   testPrecedence("1+2", HIGH);
   testPrecedence("1+2-3", HIGH, HIGH);
   testPrecedence("1+2*3", MEDIUM, HIGH);
   testPrecedence("1+2*3-4", MEDIUM, HIGH, MEDIUM);
   testPrecedence("1*2+3*4", HIGH, MEDIUM, HIGH);
        
   testPrecedence("1++2", MEDIUM, HIGH);
   testPrecedence("3*-4", MEDIUM, HIGH);
   testPrecedence("-3+4", HIGH, MEDIUM);
   testPrecedence("1 < 2 && 3 <= 4 && 5 == 6", HIGH, MEDIUM, HIGH, MEDIUM, HIGH);
       
   
   testPrecedence("1 < 2 && 3 <= 4 && 5 == 6 + 8", HIGH, LOW, HIGH, LOW, MEDIUM, HIGH);
        
   testPrecedence("x+6*4", MEDIUM, HIGH);
   testPrecedence("getX()+6", null, null, HIGH);
   testPrecedence("getX()+6*4", null, null, MEDIUM, HIGH);
        
   testPrecedence("a+b+c", HIGH, HIGH);
   testPrecedence("a.b.c", DOT, DOT);
   testPrecedence("a.b+c", DOT, HIGH);
   testPrecedence("a+b.c", HIGH, DOT);
   testPrecedence("a+b.c*d.e-f", MEDIUM, DOT, HIGH, DOT, MEDIUM);     
   }
    
   
@Test
   
public void testDot()
   {        
   testInsert(".", "{}.{$}");
       
   testInsert("0.", "{0.$}");
       
   testInsert("a.", "{a}.{$}");
       
   testInsert("foo()", "{foo}_({})_{$}");
       
   testInsert("foo().bar()", "{foo}_({})_{}.{bar}_({})_{$}");
       
   
   
   testInsert("foo+().", "{foo}+{}_({})_{}.{$}");
        
       
   testMultiInsert("foo(){.}a", "{foo}_({})_{$a}", "{foo}_({})_{}.{$a}");
        
       
   testInsert("foo()0.", "{foo}_({})_{0.$}");
       
   testBackspace("foo()0\b.", "{foo}_({})_{$}.{}");     
   }

   
@Test
   
public void testOvertype()
   {        
   
   testInsertExisting("$()", "move", "{move$}_({})_{}");
       
   testInsertExisting("move$()", "(",  "{move}_({$})_{}");

       
   
   testInsertExisting("a$+z", "+", "{a}+{$}+{z}");
       
   testInsertExisting("a$+z", "+b", "{a}+{b$}+{z}");

       
   
   testInsertExisting("a$,", ",", "{a},{$}");
       
   testInsertExisting("a$,b", ",", "{a},{$},{b}");     
   }
    
   
@Test
   
public void testSemicolon()
   {        
   testInsert(";", "{$}");
       
   testInsert("foo();", "{foo}_({})_{$}");
       
   testInsert("\";", "{}_\";$\"_{}");     
   } 
}
top, use, map, class TestExpressionSlot

.   apply
.   evaluate

top, use, map, class CompareWrapper

.   CompareWrapper
.   equals
.   toString
.   testInsert
.   testMultiInsert
.   testInsertExisting
.   testBackspace
.   testBackspace
.   testDeleteSelection
.   testSelectionInsert

top, use, map, class CPM

.   CPM
.   toString
.   testCaretPosMap
.   testCaretPosMap
.   testOperators
.   testNew
.   testStrings
.   testBrackets
.   testBackspace
.   testFloating
.   testDeleteBracket
.   testDeleteSelection
.   testSelectionOperation
.   makeCaretPos
.   testCaretPosMap
.   testPrecedence
.   testPrecedence
.   testDot
.   testOvertype
.   testSemicolon




1208 neLoCode + 0 LoComm