/*
 * CTokenMarker.java - C token marker
 * Copyright (C) 1998, 1999 Slava Pestov
 *
 * You may use and modify this package for any purpose. Redistribution is
 * permitted, in both source and binary form, provided that this notice
 * remains intact in all source distributions of this package.
 */

package processing.app.syntax;

import javax.swing.text.Segment;

/**
 * C token marker.
 *
 * @author Slava Pestov
 * @version $Id: CTokenMarker.java 1268 2005-04-09 02:30:37Z benfry $
 */
public class CTokenMarker extends TokenMarker
{
        public CTokenMarker()
        {
                this(true,getKeywords());
        }

        public CTokenMarker(boolean cpp, KeywordMap keywords)
        {
                this.cpp = cpp;
                this.keywords = keywords;
        }

        public byte markTokensImpl(byte token, Segment line, int lineIndex)
        {
                char[] array = line.array;
                int offset = line.offset;
                lastOffset = offset;
                lastKeyword = offset;
                int mlength = line.count + offset;
                boolean backslash = false;

loop:           for(int i = offset; i < mlength; i++)
                {
                        int i1 = (i+1);

                        char c = array[i];
                        if(c == '\\')
                        {
                                backslash = !backslash;
                                continue;
                        }

                        switch(token)
                        {
                        case Token.NULL:
                                switch(c)
                                {
                                case '#':
                                        if(backslash)
                                                backslash = false;
                                        else if(cpp)
                                        {
                                                if(doKeyword(line,i,c))
                                                        break;
                                                addToken(i - lastOffset,token);
                                                addToken(mlength - i,Token.KEYWORD2);
                                                lastOffset = lastKeyword = mlength;
                                                break loop;
                                        }
                                        break;
                                case '"':
                                        doKeyword(line,i,c);
                                        if(backslash)
                                                backslash = false;
                                        else
                                        {
                                                addToken(i - lastOffset,token);
                                                token = Token.LITERAL1;
                                                lastOffset = lastKeyword = i;
                                        }
                                        break;
                                case '\'':
                                        doKeyword(line,i,c);
                                        if(backslash)
                                                backslash = false;
                                        else
                                        {
                                                addToken(i - lastOffset,token);
                                                token = Token.LITERAL2;
                                                lastOffset = lastKeyword = i;
                                        }
                                        break;
                                case ':':
                                        if(lastKeyword == offset)
                                        {
                                                if(doKeyword(line,i,c))
                                                        break;
                                                backslash = false;
                                                addToken(i1 - lastOffset,Token.LABEL);
                                                lastOffset = lastKeyword = i1;
                                        }
                                        else if(doKeyword(line,i,c))
                                                break;
                                        break;
                                case '/':
                                        backslash = false;
                                        doKeyword(line,i,c);
                                        if(mlength - i > 1)
                                        {
                                                switch(array[i1])
                                                {
                                                case '*':
                                                        addToken(i - lastOffset,token);
                                                        lastOffset = lastKeyword = i;
                                                        if(mlength - i > 2 && array[i+2] == '*')
                                                                token = Token.COMMENT2;
                                                        else
                                                                token = Token.COMMENT1;
                                                        break;
                                                case '/':
                                                        addToken(i - lastOffset,token);
                                                        addToken(mlength - i,Token.COMMENT1);
                                                        lastOffset = lastKeyword = mlength;
                                                        break loop;
                                                }
                                        }
                                        break;
                                default:
                                        backslash = false;
                                        if(!Character.isLetterOrDigit(c)
                                                && c != '_')
                                                doKeyword(line,i,c);
                                        break;
                                }
                                break;
                        case Token.COMMENT1:
                        case Token.COMMENT2:
                                backslash = false;
                                if(c == '*' && mlength - i > 1)
                                {
                                        if(array[i1] == '/')
                                        {
                                                i++;
                                                addToken((i+1) - lastOffset,token);
                                                token = Token.NULL;
                                                lastOffset = lastKeyword = i+1;
                                        }
                                }
                                break;
                        case Token.LITERAL1:
                                if(backslash)
                                        backslash = false;
                                else if(c == '"')
                                {
                                        addToken(i1 - lastOffset,token);
                                        token = Token.NULL;
                                        lastOffset = lastKeyword = i1;
                                }
                                break;
                        case Token.LITERAL2:
                                if(backslash)
                                        backslash = false;
                                else if(c == '\'')
                                {
                                        addToken(i1 - lastOffset,Token.LITERAL1);
                                        token = Token.NULL;
                                        lastOffset = lastKeyword = i1;
                                }
                                break;
                        default:
                                throw new InternalError("Invalid state: "
                                        + token);
                        }
                }

                if(token == Token.NULL)
                        doKeyword(line,mlength,'\0');

                switch(token)
                {
                case Token.LITERAL1:
                case Token.LITERAL2:
                        addToken(mlength - lastOffset,Token.INVALID);
                        token = Token.NULL;
                        break;
                case Token.KEYWORD2:
                        addToken(mlength - lastOffset,token);
                        if (!backslash) token = Token.NULL;
                        addToken(mlength - lastOffset,token);
                        break;
                default:
                        addToken(mlength - lastOffset,token);
                        break;
                }

                return token;
        }

        public static KeywordMap getKeywords()
        {
                if(cKeywords == null)
                {
                        cKeywords = new KeywordMap(false);
                        cKeywords.add("char",Token.KEYWORD3);
                        cKeywords.add("double",Token.KEYWORD3);
                        cKeywords.add("enum",Token.KEYWORD3);
                        cKeywords.add("float",Token.KEYWORD3);
                        cKeywords.add("int",Token.KEYWORD3);
                        cKeywords.add("long",Token.KEYWORD3);
                        cKeywords.add("short",Token.KEYWORD3);
                        cKeywords.add("signed",Token.KEYWORD3);
                        cKeywords.add("struct",Token.KEYWORD3);
                        cKeywords.add("typedef",Token.KEYWORD3);
                        cKeywords.add("union",Token.KEYWORD3);
                        cKeywords.add("unsigned",Token.KEYWORD3);
                        cKeywords.add("void",Token.KEYWORD3);
                        cKeywords.add("auto",Token.KEYWORD1);
                        cKeywords.add("const",Token.KEYWORD1);
                        cKeywords.add("extern",Token.KEYWORD1);
                        cKeywords.add("register",Token.KEYWORD1);
                        cKeywords.add("static",Token.KEYWORD1);
                        cKeywords.add("volatile",Token.KEYWORD1);
                        cKeywords.add("break",Token.KEYWORD1);
                        cKeywords.add("case",Token.KEYWORD1);
                        cKeywords.add("continue",Token.KEYWORD1);
                        cKeywords.add("default",Token.KEYWORD1);
                        cKeywords.add("do",Token.KEYWORD1);
                        cKeywords.add("else",Token.KEYWORD1);
                        cKeywords.add("for",Token.KEYWORD1);
                        cKeywords.add("goto",Token.KEYWORD1);
                        cKeywords.add("if",Token.KEYWORD1);
                        cKeywords.add("return",Token.KEYWORD1);
                        cKeywords.add("sizeof",Token.KEYWORD1);
                        cKeywords.add("switch",Token.KEYWORD1);
                        cKeywords.add("while",Token.KEYWORD1);
                        cKeywords.add("asm",Token.KEYWORD2);
                        cKeywords.add("asmlinkage",Token.KEYWORD2);
                        cKeywords.add("far",Token.KEYWORD2);
                        cKeywords.add("huge",Token.KEYWORD2);
                        cKeywords.add("inline",Token.KEYWORD2);
                        cKeywords.add("near",Token.KEYWORD2);
                        cKeywords.add("pascal",Token.KEYWORD2);
                        cKeywords.add("true",Token.LITERAL2);
                        cKeywords.add("false",Token.LITERAL2);
                        cKeywords.add("NULL",Token.LITERAL2);
                }
                return cKeywords;
        }

        // private members
        private static KeywordMap cKeywords;

        private boolean cpp;
        private KeywordMap keywords;
        private int lastOffset;
        private int lastKeyword;

        private boolean doKeyword(Segment line, int i, char c)
        {
                int i1 = i+1;

                int len = i - lastKeyword;
                byte id = keywords.lookup(line,lastKeyword,len);
                if(id != Token.NULL)
                {
                        if(lastKeyword != lastOffset)
                                addToken(lastKeyword - lastOffset,Token.NULL);
                        addToken(len,id);
                        lastOffset = i;
                }
                lastKeyword = i1;
                return false;
        }
}
