001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools.template_engine;
003
004import java.util.Arrays;
005import java.util.HashSet;
006import java.util.Set;
007
008public class Tokenizer {
009
010    public static class Token {
011        private final TokenType type;
012        private final int position;
013        private final String text;
014
015        public Token(TokenType type, int position) {
016            this(type, position, null);
017        }
018
019        public Token(TokenType type, int position, String text) {
020            this.type = type;
021            this.position = position;
022            this.text = text;
023        }
024
025        public TokenType getType() {
026            return type;
027        }
028
029        public int getPosition() {
030            return position;
031        }
032
033        public String getText() {
034            return text;
035        }
036
037        @Override
038        public String toString() {
039            return type + (text != null ? ' ' + text : "");
040        }
041    }
042
043    public enum TokenType { CONDITION_START, VARIABLE_START, CONTEXT_SWITCH_START, END, PIPE, APOSTROPHE, TEXT, EOF }
044
045    private final Set<Character> specialCharaters = new HashSet<>(Arrays.asList(new Character[] {'$', '?', '{', '}', '|', '\'', '!'}));
046
047    private final String template;
048
049    private int c;
050    private int index;
051    private Token currentToken;
052    private final StringBuilder text = new StringBuilder();
053
054    public Tokenizer(String template) {
055        this.template = template;
056        getChar();
057    }
058
059    private void getChar() {
060        if (index >= template.length()) {
061            c = -1;
062        } else {
063            c = template.charAt(index++);
064        }
065    }
066
067    public Token nextToken() throws ParseError {
068        if (currentToken != null) {
069            Token result = currentToken;
070            currentToken = null;
071            return result;
072        }
073        int position = index;
074
075        text.setLength(0);
076        switch (c) {
077        case -1:
078            return new Token(TokenType.EOF, position);
079        case '{':
080            getChar();
081            return new Token(TokenType.VARIABLE_START, position);
082        case '?':
083            getChar();
084            if (c == '{') {
085                getChar();
086                return new Token(TokenType.CONDITION_START, position);
087            } else
088                throw ParseError.unexpectedChar('{', (char) c, position);
089        case '!':
090            getChar();
091            if (c == '{') {
092                getChar();
093                return new Token(TokenType.CONTEXT_SWITCH_START, position);
094            } else
095                throw ParseError.unexpectedChar('{', (char) c, position);
096        case '}':
097            getChar();
098            return new Token(TokenType.END, position);
099        case '|':
100            getChar();
101            return new Token(TokenType.PIPE, position);
102        case '\'':
103            getChar();
104            return new Token(TokenType.APOSTROPHE, position);
105        default:
106            while (c != -1 && !specialCharaters.contains((char) c)) {
107                if (c == '\\') {
108                    getChar();
109                    if (c == 'n') {
110                        c = '\n';
111                    }
112                }
113                text.append((char) c);
114                getChar();
115            }
116            return new Token(TokenType.TEXT, position, text.toString());
117        }
118    }
119
120    public Token lookAhead() throws ParseError {
121        if (currentToken == null) {
122            currentToken = nextToken();
123        }
124        return currentToken;
125    }
126
127    public Token skip(char lastChar) {
128        currentToken = null;
129        int position = index;
130        StringBuilder result = new StringBuilder();
131        while (c != lastChar && c != -1) {
132            if (c == '\\') {
133                getChar();
134            }
135            result.append((char) c);
136            getChar();
137        }
138        return new Token(TokenType.TEXT, position, result.toString());
139    }
140}