1

I am making a custom language as a project for a class called Compilers. The whole project is in Java, using JFlex as my lexical analyzer, and Cup as my syntactic analyzer.

I created a simple text editor for the language, which basically consists of a JTextPane, where the user can type the custom code that will be parsed. This JTextPane has a DefaultStyledDocument, used to set character attributes, e.g. change the color for keywords, comments, strings, numbers, etc., for the code (text) inside the JTextPane.

Here is the code I am using:

        DefaultStyledDocument doc = new DefaultStyledDocument() {
        @Override
        public void insertString(int offset, String str, AttributeSet a) throws BadLocationException { //cuando se insertan caracteres.
            super.insertString(offset, str, a);
            String text = getText(0, getLength());
            syntax = new SyntaxHighlighter(new java.io.StringReader(text));
            Token val;
            try {
                while ((val = syntax.yylex()) != null) {
                    switch (val.type) {
                        case TokenType.KEYWORD:
                            setCharacterAttributes(val.start, val.length, keyword, true);
                            break;
                        case TokenType.COMMENT:
                            setCharacterAttributes(val.start, val.length, comment, true);
                            break;
                        case TokenType.STRING:
                            setCharacterAttributes(val.start, val.length, string, true);
                            break;
                        case TokenType.FUNCTION:
                            setCharacterAttributes(val.start, val.length, function, true);
                            break;
                        case TokenType.NUMBER:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                        case TokenType.OPERATOR:
                            setCharacterAttributes(val.start, val.length, operator, true);
                            break;
                        case TokenType.READ:
                            setCharacterAttributes(val.start, val.length, number, true);
                            break;
                        default:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                    }
                }
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(rootPane, "Oops! Exception triggered\n" + ex.getMessage());
            }
        }

        @Override
        //this is the method I want to optimize
        public void remove(int offs, int len) throws BadLocationException { 
            super.remove(offs, len);
            String text = getText(0, getLength());
            syntax = new SyntaxHighlighter(new java.io.StringReader(text));
            Token val;
            try {
                while ((val = syntax.yylex()) != null) {
                    switch (val.type) {
                        case TokenType.KEYWORD:
                            setCharacterAttributes(val.start, val.length, keyword, true);
                            break;
                        case TokenType.COMMENT:
                            setCharacterAttributes(val.start, val.length, comment, true);
                            break;
                        case TokenType.STRING:
                            setCharacterAttributes(val.start, val.length, string, true);
                            break;
                        case TokenType.FUNCTION:
                            setCharacterAttributes(val.start, val.length, function, true);
                            break;
                        case TokenType.NUMBER:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                        case TokenType.OPERATOR:
                            setCharacterAttributes(val.start, val.length, operator, true);
                            break;
                        case TokenType.READ:
                            setCharacterAttributes(val.start, val.length, number, true);
                            break;
                        default:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                    }
                }
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(rootPane, "Oops! Exception triggered\n" + ex.getMessage());
            }
        }
    };

this.codeTextPane.setStyledDocument(doc);

The SyntaxHighlighter class is basically a lexer (made with JFlex) used only as a way to search specific pieces of text (keywords, strings, etc). Everything works perfectly, but...

The problem:

When the JTextPane has a decent amount of text in it, holding down the backspace key to remove text makes the program lag pretty hard. The reason I think this happens is probably because the SyntaxHighlighter runs with every character being removed, because holding down the backspace key calls the remove() function for each character being deleted. Inserting text is not a problem really because you can either load the code from a file (where the whole text in that file will be analyzed by the SyntaxHighlighter as a whole), or you just can't type fast enough to notice the lag.

Is there a way I can optimize this? Thank you all!

  • 2
    Might be more appropriate in CodeReview than SO – Dragondraikk May 12 '15 at 15:07
  • I'd separate the highlighting of the text from the removal of the characters. – duffymo May 12 '15 at 15:08
  • I think I would seriously re-write this code. Insert/delete operations should do *just that.* I think I'd run the lexer/analyzer on a background thread so it didn't lag the main app. – markspace May 12 '15 at 15:09
  • 2
    Maybe @Dragondraikk. It's hard to say how this would be received on Code Review. Some might say the code is broken. Other's might say it just needs optimized. – RubberDuck May 12 '15 at 15:11
  • 1
    The syntax highlighter gets run in your paintComponent method right? It should anyway. So what I was going to recommend is that in your character removal / change call to where the SyntaxHighlighter gets run again, use repaint(1000) which will delay (maybe) the immediate repaint and run of the SyntaxHighligher. I hope that's clear – ControlAltDel May 12 '15 at 15:15
  • @ControlAltDel I'd rather try an only slightly delayed `repaint`. You still have the advantage that multiple requests get collapsed. And maybe moving the highlighter out of `paintComponent` to a separate thread could be a good idea in the end... – maaartinus May 12 '15 at 16:52
  • What about syntax highliting only on key release instead of removal ? – Sharcoux May 13 '15 at 06:49

3 Answers3

1

My first instinct would be to employ a windowing strategy. In your code maintain a tree or some other structure capable of representing self-contained scope. Then adapt the syntax highlighter and other parts of the code to work only on the parts of the tree (or whatever) they know are affected.

Abstractly, you might have a relationship like this:

class
  |
+----------+
method1    method2
              |
           +--------+--------+
           line1    line2    line3

... which allows a delete in line3 to be understood in the context of what doesn't change

Trevor Brown
  • 169
  • 3
0

All in all this code seems to be structured and clear I understood it very fast. So my suggestion: leave it as it is as long as possible (forcing open-closed principle).

The only change I would suggest is to separate character removal from highlighting. This was already mentioned. But the reason why you should do this is: You will be able to delay syntax highlighting until the user has remove a chunk of characters. So syntax highlighting will not be trigerred every time you remove only one char.

I think you should break apart the main text into syntactical units and then apply the syntax highlighting only on changed syntactical units. The main problem is the parsing and the identification of the changed unit.

As a previous author mentioned isolating syntax highlighting into a separated thread will improve performance as well.

These changes are not trivial but possible.

oopexpert
  • 767
  • 7
  • 12
0

Define the class field javax.swing.Timer syntaxTimer.

On each insert() or remove() check whether the syntaxTimer is null or not.

If it's null create the syntaxTimer instance with 500 msec delay (could be 300 or 1000 as you wish) and start the syntaxTimer. If not just skip the timer initialization because you already have it initialized.

When the syntaxTimer's actionPerformed() is called do your SyntaxHighlighter related logic and clear the syntaxTimer (stop it and set the syntaxTimer=null).

StanislavL
  • 56,971
  • 9
  • 68
  • 98