package editor;
import java.awt.*;
import javax.swing.*;
import java.util.*;

public class Editor extends JTextArea
{
  static final String fontName = "Monospaced";
  static final byte normal = 0x00;
  static final byte comment = 0x01;
  static final byte error = 0x02;
  static final byte nonTerminal = 0x13;
  static final byte terminal = 0x14;
//------------------------------------------------------------------------------
  ByteArray attributes = null;
  ByteArray underLines = null;
  ObjectArray lineRules = null;
  ObjectArray lineDictionaries = null;
  IntArray lineOffsets = null;
  int key = 0;
  Analyser analyser = null;
  Color caretBorder = null;
  Color groundLighter = null;
  Color[] underLineColors = null;
  Font[] fonts = null;
  Color[] colors = null;
//------------------------------------------------------------------------------
  public Editor(int fontSize)
  { super();
    attributes = new ByteArray();
    underLines = new ByteArray();
    lineRules = new ObjectArray();
    lineDictionaries = new ObjectArray();
    lineOffsets = new IntArray();
    analyser = new Analyser(this);
    Thread thread = new Thread(analyser);
    thread.setDaemon(true);
    thread.start();
    getDocument().addDocumentListener(new EditorListener(this));
    caretBorder = new Color(192,192,192);
    groundLighter = new Color(255,244,244);
    Color[] underLineColors = { new Color(224,224,255),// Недостижимые
                                new Color(224,255,224)};// Непроизводящие
    this.underLineColors = underLineColors;
    Font[] fonts = { new Font(fontName,Font.PLAIN,fontSize),
                     new Font(fontName,Font.BOLD,fontSize),
                     new Font(fontName,Font.ITALIC,fontSize),
                     new Font(fontName,Font.BOLD|Font.ITALIC,fontSize)};
    this.fonts = fonts;
    super.setFont(fonts[0]);
    Color[] colors = { new Color(0,0,0),
                       new Color(96,160,160),
                       new Color(255,0,0),
                       new Color(0,0,192),
                       new Color(0,192,0)};
    this.colors = colors;
    super.setForeground(colors[0]);
  }
//------------------------------------------------------------------------------
  public void setFont(Font f) { }
  public void setForeground(Color fg) { }
//------------------------------------------------------------------------------
  public Point getLocationOnContentPane()
  { Container container = this;
    while(!(container instanceof JRootPane))
      container = container.getParent();
    JPanel contentPane = (JPanel)((JRootPane)container).getContentPane();
    Point paneLocationOnScreen = contentPane.getLocationOnScreen();
    Point thisLocationOnScreen = this.getLocationOnScreen();
    Point point = new Point(thisLocationOnScreen.x-paneLocationOnScreen.x,
                            thisLocationOnScreen.y-paneLocationOnScreen.y);
    return(point);
  }
//------------------------------------------------------------------------------
  public String getUIClassID()
  { return EditorUI.uiClassID; }
////////////////////////////////////////////////////////////////////////////////
  public void styleForLine(int line)
      throws javax.swing.text.BadLocationException
  { lineRules.array[line] = null;
    lineDictionaries.array[line] = null;
    int start = getLineStartOffset(line);
    int end = getLineEndOffset(line);
    if(line != (getLineCount()-1)) --end;
    Arrays.fill(attributes.array,start,end,normal);
    javax.swing.text.Segment segment = new javax.swing.text.Segment();
    getDocument().getText(start,end-start,segment);
    ArrayList textList = createTextList(segment);
    segment = null;
    int commentIndex = getCommentIndex(textList);
    if(commentIndex>=0)
    { Arrays.fill(attributes.array,start+commentIndex,end,comment);
      end = commentIndex;
    }
    convertArrow(textList);
    textList = createNewTextList(textList);
    if(textList.size()<=0) return;
    ArrayList headerList = getHeaderList(textList);
    if(headerList==null)
    { fillIfNoHeader(textList,start);
      setGroundLighterEnabled(true,start);
      return;
    }
    IndexedString header = fillHeader(headerList,start);
    headerList = null;
    ArrayList rightParts = convertRightPart(textList);
    textList = null;
    int partCount = rightParts.size();
    fillDividers(rightParts,start);
    fillParts(rightParts,start);
    if((header==null)||(partCount!=rightParts.size()))
    { setGroundLighterEnabled(true,start);
      return;
    }
    IndexedString[][] ruleArray = createRuleArray(header,rightParts);
    header = null;
    rightParts = null;
    lineRules.array[line] = ruleArray;
  }
//------------------------------------------------------------------------------
  ArrayList createTextList(javax.swing.text.Segment segment)
  {
    // Имеется 3 вида кавычек: <>, "", ''.
    // Символы от открывающей до закрывающей могут быть абсолютно любыми.
    // Функция возвращает ArrayList объектов либо Character, либо StringBuffer.
    // Все StringBuffer начинаются с одной из 3-х видов открывающих кавычек.
    // Все StringBuffer завершаются завершающей кавычкой, того же типа,
    // что и открывающая. Может быть и исключение без завершающей кавычки -
    // последний StringBuffer, если он последний в ArrayList.
    // В одном StringBuffer может быть только один кусок текста в кавычках.

    int begin = segment.getBeginIndex();
    int end = segment.getEndIndex();
    ArrayList arrayList = new ArrayList(end-begin);
    StringBuffer stringBuffer = null;
    for(int i=begin;i<end;++i)
    { char c = segment.array[i];
      if(stringBuffer==null)
      { switch(c)
        { case '<':
            stringBuffer = new StringBuffer();
            arrayList.add(stringBuffer);
            stringBuffer.append(c);
          break;
          case '\"':
            stringBuffer = new StringBuffer();
            arrayList.add(stringBuffer);
            stringBuffer.append(c);
          break;
          case '\'':
            stringBuffer = new StringBuffer();
            arrayList.add(stringBuffer);
            stringBuffer.append(c);
          break;
          default:
            arrayList.add(new Character(c));
          break;
        }
      }
      else
      { stringBuffer.append(c);
        switch(c)
        { case '>':
            if(stringBuffer.charAt(0)=='<') stringBuffer = null;
          break;
          case '\"':
            if(stringBuffer.charAt(0)=='\"') stringBuffer = null;
          break;
          case '\'':
            if(stringBuffer.charAt(0)=='\'') stringBuffer = null;
          break;
        }
      }
    }
    return(arrayList);
  }
//------------------------------------------------------------------------------
  int getCommentIndex(ArrayList textList)
  {
    // Возвращает индекс первого элемента комментария, или -1,
    // если комментария нет. Если комментарий есть,
    // то все его символы удаляются из textList.

    int size = textList.size();
    StringBuffer stringBuffer = new StringBuffer(size);
    for(int i=0;i<size;++i)
    { Object object = textList.get(i);
      if(object instanceof Character)
        stringBuffer.append(((Character)object).charValue());
      else stringBuffer.append(' ');
    }
    int commentIndex = stringBuffer.indexOf("--");
    if(commentIndex>=0)
    { for(int i=size-1;i>=commentIndex;--i)
        textList.remove(i);
      commentIndex = 0;
      size = textList.size();
      for(int i=0;i<size;++i)
      { Object object = textList.get(i);
        if(object instanceof Character) ++commentIndex;
        else commentIndex+=((StringBuffer)object).length();
      }
    }
    return(commentIndex);
  }
//------------------------------------------------------------------------------
  void convertArrow(ArrayList textList)
  {
    // Функция ищет первую последовательность символов "::=".
    // Если такая последовательность есть,
    // то в textList 3 соответствующих Character заменяются на 1 StringBuffer.
    // Таким образом, это будет единственный StringBuffer, начинающийся с ":".

    int size = textList.size();
    StringBuffer stringBuffer = new StringBuffer(size);
    for(int i=0;i<size;++i)
    { Object object = textList.get(i);
      if(object instanceof Character)
        stringBuffer.append(((Character)object).charValue());
      else stringBuffer.append(' ');
    }
    String arrow = "::=";
    int commentIndex = stringBuffer.indexOf(arrow);
    if(commentIndex>=0)
    { int length = arrow.length();
      for(int i=0;i<length;++i)
        textList.remove(commentIndex);
      textList.add(commentIndex,new StringBuffer(arrow));
    }
  }
//------------------------------------------------------------------------------
  ArrayList createNewTextList(ArrayList textList)
  {
    // Функция убирает пробелы и служебные символы.
    // Но, чтобы не потерять начальный индекс,
    // он запоминается вместе с объектом в контейнере.
    // Все StringBuffer конвертируются в String.

    int size = textList.size();
    ArrayList newTextList = new ArrayList(size);
    int index = 0;
    for(int i=0;i<size;++i)
    { Object object = textList.get(i);
      if(object instanceof Character)
      { char c = ((Character)object).charValue();
        if((c!=' ')&&(c!='\b')&&(c!='\f')&&(c!='\n')&&(c!='\r')&&(c!='\t'))
          newTextList.add(new IndexedObject(object,index));
        ++index;
      }
      else
      { newTextList.add(new IndexedObject(
            ((StringBuffer)object).toString(),index));
        index+=((StringBuffer)object).length();
      }
    }
    return(newTextList);
  }
//------------------------------------------------------------------------------
  ArrayList getHeaderList(ArrayList textList)
  {
    // Функция ищет String с разделителем частей правил "::=".
    // Если такого разделителя в строке нет, то функция возвращает null.
    // Если такой разделитель есть, то все, что перед ним было,
    // помещается в отдельный ArrayList, который и возвращается.
    // При этом элементы не копируются, а перемещаются.
    // То есть из textList они удалаются.
    // Сам разделитель "::=" тоже удаляется.

    int size = textList.size();
    int index = -1;
    for(int i=0;(i<size)&&(index<0);++i)
    { Object object = ((IndexedObject)textList.get(i)).object;
      if((object instanceof String)&&(((String)object).charAt(0)==':'))
        index = i;
    }
    ArrayList headerList = null;
    if(index>=0)
    { headerList = new ArrayList(index);
      for(int i=0;i<index;++i)
      { headerList.add(textList.get(0));
        textList.remove(0);
      }
      textList.remove(0);
    }
    return(headerList);
  }
//------------------------------------------------------------------------------
  void setGroundLighterEnabled(boolean enabled,int start)
  { byte startByte = attributes.array[start];
    startByte&=0x3f;
    if(enabled) startByte|=0x40;
    attributes.array[start] = startByte;
  }
//------------------------------------------------------------------------------
  void fillIfNoHeader(ArrayList textList,int start)
  {
    // Функция раскрашивает текст в случае, если разделитель "::=" не найден.
    // Особенность в том, что в конце могут быть одно или два ':',
    // которые должны быть нормального цвета.

    int size = textList.size();
    int index = -1;
    if(size>0)
    { IndexedObject io = (IndexedObject)textList.get(size-1);
      if((io.object instanceof Character)
         &&(((Character)io.object).charValue()==':'))
      { --size; index = io.index; }
    }
    if((size>0)&&(index>=0))
    { IndexedObject io = (IndexedObject)textList.get(size-1);
      if((io.object instanceof Character)
         &&(((Character)io.object).charValue()==':')&&(io.index==index-1))
        --size;
    }
    boolean already = false;
    for(int i=0;i<size;++i)
    { IndexedObject io = (IndexedObject)textList.get(i);
      if(io.object instanceof Character)
        Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
      else
      { String string = (String)io.object;
        int length = string.length();
        char last = string.charAt(length-1);
        if((!already)&&(string.charAt(0)=='<')
            &&(((last=='>')&&(length>2))||(last!='>')))
        { already = true;
          Arrays.fill(attributes.array,start+io.index,
                      start+io.index+length,nonTerminal);
        }
        else
          Arrays.fill(attributes.array,start+io.index,start+io.index+length,error);
      }
    }
  }
//------------------------------------------------------------------------------
  IndexedString fillHeader(ArrayList headerList,int start)
  {
    // Функция раскрашивает левую часть правила.
    // Если эта левая часть корректна, т.е. содержит только нетерминал,
    // то функция возвращает IndexedString, соответствующий этому нетерминалу.
    // Иначе возвращает null.

    int size = headerList.size();
    if(size==1)
    { IndexedObject io = (IndexedObject)headerList.get(0);
      if(io.object instanceof String)
      { String string = (String)io.object;
        int length = string.length();
        if((string.charAt(0)=='<')&&(length>2))
        { Arrays.fill(attributes.array,start+io.index,
                      start+io.index+length,nonTerminal);
          return(new IndexedString(string,io.index));
        }
      }
    }
    boolean already = false;
    for(int i=0;i<size;++i)
    { IndexedObject io = (IndexedObject)headerList.get(i);
      if(io.object instanceof Character)
        Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
      else
      { String string = (String)io.object;
        int length = string.length();
        if((!already)&&(string.charAt(0)=='<')&&(length>2))
        { already = true;
          Arrays.fill(attributes.array,start+io.index,
                      start+io.index+length,nonTerminal);
        }
        else
          Arrays.fill(attributes.array,start+io.index,start+io.index+length,error);
      }
    }
    return(null);
  }
//------------------------------------------------------------------------------
  ArrayList convertRightPart(ArrayList textList)
  {
    // Функция разделяет правую часть правила на несколько простых частей,
    // т.е. частей, не содержащих разделителя "|".
    // Полученные части располагаются в ArrayList, которые в свою очередь
    // находятся в объектах RightPartOfRule.
    // Разделители тоже помещаются в RightPartOfRule.
    // Причем в объект RightPartOfRule помещается разделитель,
    // следующий в тексте сразу же за объектами ArrayList,
    // находящемся в том же RightPartOfRule.
    // Поэтому у последнего RightPartOfRule поле разделителя будет равно null.

    ArrayList parts = new ArrayList();
    parts.add(new RightPartOfRule());
    int size = textList.size();
    for(int i=0;i<size;++i)
    { IndexedObject io = (IndexedObject)textList.get(i);
      RightPartOfRule part = (RightPartOfRule)parts.get(parts.size()-1);
      if((io.object instanceof Character)
         &&(((Character)io.object).charValue()=='|'))
      { part.divider = io;
        parts.add(new RightPartOfRule());
      }
      else part.list.add(io);
    }
    return(parts);
  }
//------------------------------------------------------------------------------
  void fillDividers(ArrayList rightParts,int start)
  {
    // Функция ищет пустые правые части и раскрашивает их разделители.
    // Сами пустые правые части из rightParts удаляются.

    int size = rightParts.size();
    for(int i=0;i<size;++i)
    { RightPartOfRule part = (RightPartOfRule)rightParts.get(i);
      if(part.list.size()<=0)
      { if(part.divider!=null)
          Arrays.fill(attributes.array,start+part.divider.index,
                      start+part.divider.index+1,error);
        rightParts.remove(i); --i; --size;
      }
    }
  }
//------------------------------------------------------------------------------
  void fillParts(ArrayList rightParts,int start)
  {
    // Функция вызывает другую функцию для раскраски правых частей правил
    // и удаляет некорректные правые части из rightParts.

    int size = rightParts.size();
    for(int i=0;i<size;++i)
    { RightPartOfRule part = (RightPartOfRule)rightParts.get(i);
      if(!fillPart(part.list,start))
      { rightParts.remove(i); --i; --size; }
    }
  }
//------------------------------------------------------------------------------
  boolean fillPart(ArrayList list,int start)
  {
    // Функция раскрашивает одну правую часть правила и возвращает true,
    // если эта часть корректна, иначе - false.

    boolean OK = true;
    int size = list.size();
    for(int i=0;i<size;++i)
    { IndexedObject io = (IndexedObject)list.get(i);
      if(io.object instanceof Character)
      { switch(((Character)io.object).charValue())
        { case '$':
            if(size!=1)
             { Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
               OK = false;
             }
          break;
          case ':':
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
            OK = false;
          break;
          case ',':
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
            OK = false;
          break;
          case '(':
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
            OK = false;
          break;
          case ')':
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
            OK = false;
          break;
          case '>':
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,error);
            OK = false;
          break;
          default:
            Arrays.fill(attributes.array,start+io.index,start+io.index+1,terminal);
          break;
        }
      }
      else
      { String string = (String)io.object;
        int length = string.length();
        char first = string.charAt(0);
        char last = string.charAt(length-1);
        switch(first)
        { case '<':
            if(last!='>')
            { Arrays.fill(attributes.array,start+io.index,
                          start+io.index+length,nonTerminal);
              OK = false;
            }
            else
            { if(length>2)
                Arrays.fill(attributes.array,start+io.index,
                            start+io.index+length,nonTerminal);
              else
              { Arrays.fill(attributes.array,start+io.index,
                            start+io.index+length,error);
                OK = false;
              }
            }
          break;
          default:// ВНИМАНИЕ! Нужна Дополнительная Проверка Длины!
                  // Так как открывающие и закрывающие символы одинаковые!
            if(last!=first)
            { Arrays.fill(attributes.array,start+io.index,
                          start+io.index+length,terminal);
              OK = false;
            }
            else
            { if(length>2)
                Arrays.fill(attributes.array,start+io.index,
                            start+io.index+length,terminal);
              else
              { if(length==2)
                { Arrays.fill(attributes.array,start+io.index,
                              start+io.index+length,error);
                  OK = false;
                }
                else
                { Arrays.fill(attributes.array,start+io.index,
                              start+io.index+length,terminal);
                  OK = false;
                }
              }
            }
          break;
        }
      }
    }
    return(OK);
  }
//------------------------------------------------------------------------------
  IndexedString[][] createRuleArray(IndexedString header,ArrayList rightParts)
  { int size = rightParts.size();
    IndexedString[][] ruleArray = new IndexedString[size][];
    for(int i=0;i<size;++i)
    { RightPartOfRule part = (RightPartOfRule)rightParts.get(i);
      ruleArray[i] = createRule(header,part.list);
    }
    return(ruleArray);
  }
//------------------------------------------------------------------------------
  IndexedString[] createRule(IndexedString header,ArrayList list)
  {
    // Особенность функции в том, что в строках
    // открывающие и закрывающие кавычки всегда будут двойными.

    int size = list.size();
    IndexedString[] rule = new IndexedString[size+1];
    rule[0] = header;
    for(int i=0;i<size;++i)
    { IndexedObject io = (IndexedObject)list.get(i);
      if(io.object instanceof Character)
      { String string = ((Character)io.object).toString();
        rule[i+1] = new IndexedString(string,io.index);
      }
      else
      { String string = (String)io.object;
        if(string.charAt(0)=='\'')
          string = '\"'+string.substring(1,string.length()-1)+'\"';
        rule[i+1] = new IndexedString(string,io.index);
      }
    }
    return(rule);
  }
//------------------------------------------------------------------------------
}
//##############################################################################
class Analyser implements Runnable
{
  Editor editor;
  Analyser(Editor editor)
  { this.editor = editor; }
//------------------------------------------------------------------------------
  public void run()
  {
    System.out.println("run()!!!");
    ByteArray underLines = null;
    ArrayList ruleArray = null;
    int key = 0;
    for(;;)
    { synchronized(this)
      { if((underLines!=null)&&(key==editor.key)
           &&(underLines.size==editor.underLines.size))
        { editor.underLines = underLines;
          underLines = null;
          ruleArray = null;
          key = 0;
          SwingUtilities.invokeLater(new Runnable()
          { public void run() { editor.repaint(); }});
          try { this.wait(); }
          catch(InterruptedException e) { }
        }
        underLines = new ByteArray();
        underLines.insert(0,editor.underLines.size);
        Arrays.fill(underLines.array,0,underLines.size,(byte)0);
        ruleArray = new ArrayList(editor.lineRules.size);
        for(int i=0;i<editor.lineRules.size;++i)
        { IndexedString[][] indexedArray
              = (IndexedString[][])editor.lineRules.array[i];
          int start = editor.lineOffsets.array[i];
          if(indexedArray!=null)
          { for(int j=0;j<indexedArray.length;++j)
            { IndexedString[] indexedStrings = indexedArray[j];
              IndexedString[] newIndexedStrings
                  = new IndexedString[indexedStrings.length];
              for(int k=0;k<indexedStrings.length;++k)
              { IndexedString indexedString = indexedStrings[k];
                newIndexedStrings[k] = new IndexedString(indexedString.string,
                                                         indexedString.index+start);
              }
              ruleArray.add(newIndexedStrings);
            }
          }
        }
        key = editor.key;
      }
      Hashtable elementHashTable = createElementHashTable(ruleArray);
      HashSet hashSet = createHashSet0x01(ruleArray);
      fillStrings(elementHashTable,hashSet,underLines.array,(byte)1);
      hashSet = createHashSet0x02(ruleArray);
      fillStrings(elementHashTable,hashSet,underLines.array,(byte)2);
      hashSet = null;
      elementHashTable = null;
    }
  }
//------------------------------------------------------------------------------
  Hashtable createElementHashTable(ArrayList ruleArray)
  { Hashtable elementHashTable = new Hashtable();
    for(int i=0;i<ruleArray.size();++i)
    { IndexedString[] indexedStrings = (IndexedString[])ruleArray.get(i);
      for(int j=0;j<indexedStrings.length;++j)
      { IndexedString indexedString = indexedStrings[j];
        if(!indexedString.string.equals("$"))
        { ArrayList indexList = (ArrayList)elementHashTable.get(indexedString.string);
          if(indexList==null)
          { indexList = new ArrayList();
            elementHashTable.put(indexedString.string,indexList);
            indexList.add(new Integer(indexedString.index));
          }
          else indexList.add(new Integer(indexedString.index));
        }
      }
    }
    return(elementHashTable);
  }
//------------------------------------------------------------------------------
  void fillString(Hashtable elementHashTable,String string,
                  byte[] array,byte value)
  { int length = string.length();
    ArrayList indexList = (ArrayList)elementHashTable.get(string);
    if(indexList!=null)
    { int size = indexList.size();
      for(int i=0;i<size;++i)
      { int index = ((Integer)indexList.get(i)).intValue();
        value|=array[index];
        Arrays.fill(array,index,index+length,value);
      }
    }
  }
//------------------------------------------------------------------------------
  void fillStrings(Hashtable elementHashTable,HashSet hashSet,
                   byte[] array,byte value)
  { Enumeration enumeration = elementHashTable.keys();
    while(enumeration.hasMoreElements())
    { String string = (String)enumeration.nextElement();
      if(!hashSet.contains(string))
        fillString(elementHashTable,string,array,value);
    }
  }
//------------------------------------------------------------------------------
  HashSet createHashSet0x01(ArrayList ruleArray)// Недостижимые
  { HashSet hashSet = new HashSet();
    int size = ruleArray.size();
    int before,after;
    do
    { before = hashSet.size();
      for(int i=0;i<size;++i)
      { IndexedString[] indexedStrings = (IndexedString[])ruleArray.get(i);
        IndexedString indexedString = indexedStrings[0];
        if(i==0) hashSet.add(indexedString.string);
        if(hashSet.contains(indexedString.string))
        { for(int j=1;j<indexedStrings.length;++j)
            hashSet.add(indexedStrings[j].string);
        }
      }
      after = hashSet.size();
    } while(before!=after);
    return(hashSet);
  }
//------------------------------------------------------------------------------
  HashSet createHashSet0x02(ArrayList ruleArray)// Непроизводящие
  { HashSet hashSet = new HashSet();
    int size = ruleArray.size();
    int before,after;
    do
    { before = hashSet.size();
      for(int i=0;i<size;++i)
      { IndexedString[] indexedStrings = (IndexedString[])ruleArray.get(i);
        boolean b = true;
        for(int j=1;j<indexedStrings.length;++j)
        { String string = indexedStrings[j].string;
          if(string.charAt(0)=='<')
          { if(!hashSet.contains(string)) b = false; }
          else hashSet.add(string);
        }
        if(b) hashSet.add(indexedStrings[0].string);
      }
      after = hashSet.size();
    } while(before!=after);
    return(hashSet);
  }
//------------------------------------------------------------------------------
}
//##############################################################################
class EditorListener implements javax.swing.event.DocumentListener
{
  Editor editor;
  EditorListener(Editor editor)
  { this.editor = editor; }
////////////////////////////////////////////////////////////////////////////////
  void updateLineOffsets() throws javax.swing.text.BadLocationException
  { for(int i=0;i<editor.lineOffsets.size;++i)
      editor.lineOffsets.array[i] = editor.getLineStartOffset(i);
  }
//------------------------------------------------------------------------------
  public void insertUpdate(javax.swing.event.DocumentEvent e)
  { synchronized(editor.analyser)
    { try {
        editor.attributes.insert(e.getOffset(),e.getLength());
        editor.underLines.insert(e.getOffset(),e.getLength());
        Arrays.fill(editor.underLines.array,e.getOffset(),
                    e.getOffset()+e.getLength(),(byte)0);
        int first = editor.getLineOfOffset(e.getOffset());
        int last = editor.getLineOfOffset(e.getOffset()+e.getLength());
        int count = editor.getLineCount() - editor.lineRules.size;
        editor.lineRules.insert(first,count);
        editor.lineDictionaries.insert(first,count);
        editor.lineOffsets.insert(first,count);
        for(int i=first;i<=last;++i)
          try { editor.styleForLine(i); }
          catch(javax.swing.text.BadLocationException ble) { }
        updateLineOffsets();
        ++editor.key;
        editor.analyser.notifyAll();
      } catch(javax.swing.text.BadLocationException ble) { }
    }
  }
//------------------------------------------------------------------------------
  public void removeUpdate(javax.swing.event.DocumentEvent e)
  { synchronized(editor.analyser)
    { try {
        editor.attributes.remove(e.getOffset(),e.getLength());
        editor.underLines.remove(e.getOffset(),e.getLength());
        int line = editor.getLineOfOffset(e.getOffset());
        int count = editor.lineRules.size - editor.getLineCount();
        editor.lineRules.remove(line,count);
        editor.lineDictionaries.remove(line,count);
        editor.lineOffsets.remove(line,count);
        editor.styleForLine(line);
        updateLineOffsets();
        ++editor.key;
        editor.analyser.notifyAll();
      } catch(javax.swing.text.BadLocationException ble) { }
    }
  }
//------------------------------------------------------------------------------
  public void changedUpdate(javax.swing.event.DocumentEvent e)
  { synchronized(editor.analyser)
    { try {
        Arrays.fill(editor.underLines.array,e.getOffset(),
                  e.getOffset()+e.getLength(),(byte)0);
        int first = editor.getLineOfOffset(e.getOffset());
        int last = editor.getLineOfOffset(e.getOffset()+e.getLength());
        for(int i=first;i<=last;++i)
          try { editor.styleForLine(i); }
          catch(javax.swing.text.BadLocationException ble) { }
        ++editor.key;
        editor.analyser.notifyAll();
      } catch(javax.swing.text.BadLocationException ble) { }
    }
  }
//------------------------------------------------------------------------------
}
//##############################################################################
class IndexedString
{ String string; int index;
  IndexedString(String string,int index)
  { this.string = string; this.index = index; }
}
//##############################################################################
class IndexedObject
{ Object object; int index;
  IndexedObject(Object object,int index)
  { this.object = object; this.index = index; }
}
//##############################################################################
class RightPartOfRule
{ ArrayList list = new ArrayList();
  IndexedObject divider = null;
}
