All Downloads are FREE. Search and download functionalities are using the official Maven repository.

guru.nidi.graphviz.parse.Lexer Maven / Gradle / Ivy

There is a newer version: 0.18.1
Show newest version
/*
 * Copyright © 2015 Stefan Niederhauser ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package guru.nidi.graphviz.parse;

import javax.annotation.Nullable;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

import static guru.nidi.graphviz.parse.Token.*;
import static java.util.Locale.ENGLISH;

class Lexer {
    private static final Map KEYWORDS = new HashMap<>();

    static {
        KEYWORDS.put("strict", Token.STRICT);
        KEYWORDS.put("graph", Token.GRAPH);
        KEYWORDS.put("digraph", Token.DIGRAPH);
        KEYWORDS.put("node", Token.NODE);
        KEYWORDS.put("edge", Token.EDGE);
        KEYWORDS.put("subgraph", Token.SUBGRAPH);
    }

    private static final char CH_EOF = (char) -1;
    private final PushbackReader in;
    private char ch;
    Position pos;

    Lexer(Reader in, String name) throws IOException {
        this.in = new PushbackReader(in);
        pos = new Position(name);
        readChar();
    }

    Token token() throws IOException {
        final Token sym = symbol();
        if (sym != null) {
            readChar();
            return sym;
        }
        return numeralOrIdent();
    }

    @Nullable
    private Token symbol() throws IOException {
        switch (ch) {
            case CH_EOF:
                return new Token(EOF, ch);
            case ';':
                return new Token(SEMICOLON, ch);
            case ',':
                return new Token(COMMA, ch);
            case '{':
                return new Token(BRACE_OPEN, ch);
            case '}':
                return new Token(BRACE_CLOSE, ch);
            case '=':
                return new Token(EQUAL, ch);
            case '[':
                return new Token(BRACKET_OPEN, ch);
            case ']':
                return new Token(BRACKET_CLOSE, ch);
            case ':':
                return new Token(COLON, ch);
            case '-':
                final char next = readRawChar();
                if (next == '-') {
                    return new Token(MINUS_MINUS, "--");
                }
                if (next == '>') {
                    return new Token(ARROW, "->");
                }
                unread('-', next);
                return null;
            default:
                return null;
        }
    }

    private Token numeralOrIdent() throws IOException {
        if (ch == '-' || ch == '.' || (ch >= '0' && ch <= '9')) {
            return numeral();
        }
        return ident();
    }

    private Token numeral() throws IOException {
        final StringBuilder s = new StringBuilder();
        do {
            s.append(ch);
            readRawChar();
        } while (ch == '.' || (ch >= '0' && ch <= '9'));
        sync();
        return new Token(ID, SUB_NUMERAL, s.toString());
    }

    private Token ident() throws IOException {
        if (ch == '"') {
            return quotedIdent();
        }
        if (ch == '<') {
            return htmlIdent();
        }
        if (isIdentStart()) {
            return simpleIdent();
        }
        throw new ParserException(pos, "Found unexpected character '" + ch + "'");
    }

    private boolean isIdentStart() {
        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= 128 && ch <= 255) || ch == '_';
    }

    private Token quotedIdent() throws IOException {
        final StringBuilder s = new StringBuilder();
        readRawChar();
        while (ch != '"' && ch != CH_EOF) {
            s.append(ch);
            readRawChar();
            if (ch == '"' && s.charAt(s.length() - 1) == '\\') {
                s.replace(s.length() - 1, s.length(), "\"");
                readRawChar();
            }
            if (ch == '\n' && s.charAt(s.length() - 1) == '\\') {
                s.delete(s.length() - 1, s.length());
                readRawChar();
            }
        }
        readChar();
        return new Token(ID, SUB_QUOTED, s.toString());
    }

    private Token htmlIdent() throws IOException {
        final StringBuilder s = new StringBuilder();
        int level = 1;
        readRawChar();
        level = htmlLevel(level, ch);
        while ((ch != '>' || level > 0) && ch != CH_EOF) {
            s.append(ch);
            readRawChar();
            level = htmlLevel(level, ch);
        }
        readChar();
        return new Token(ID, SUB_HTML, s.toString());
    }

    private int htmlLevel(int level, char ch) {
        if (ch == '<') {
            return level + 1;
        }
        if (ch == '>') {
            return level - 1;
        }
        return level;
    }

    private Token simpleIdent() throws IOException {
        final StringBuilder s = new StringBuilder();
        do {
            s.append(ch);
            readRawChar();
        } while ((isIdentStart() || (ch >= '0' && ch <= '9')) && ch != CH_EOF);
        sync();
        final Integer key = KEYWORDS.get(s.toString().toLowerCase(ENGLISH));
        return key == null ? new Token(ID, SUB_SIMPLE, s.toString()) : new Token(key, s.toString());
    }

    private void sync() throws IOException {
        if (ch <= ' ') {
            readChar();
        }
    }

    private void readChar() throws IOException {
        do {
            readRawChar();
            if (ch == '/') {
                readComment();
            } else if (ch == '\n') {
                pos.newLine();
                final char next = readRawChar();
                if (next == '#') {
                    do {
                        readRawChar();
                    } while (ch != '\n' && ch != CH_EOF);
                } else {
                    unread('\n', next);
                }
            }
        } while (ch <= ' ' && ch != CH_EOF);
    }

    private void readComment() throws IOException {
        final char next = readRawChar();
        if (next == '/') {
            do {
                readRawChar();
            } while (ch != '\n' && ch != CH_EOF);
        } else if (next == '*') {
            do {
                do {
                    readRawChar();
                } while (ch != '*' && ch != CH_EOF);
                readRawChar();
            } while (ch != '/' && ch != CH_EOF);
            readRawChar();
        } else {
            unread('/', next);
        }
    }

    private char readRawChar() throws IOException {
        pos.newChar();
        return ch = (char) in.read();
    }

    private void unread(char before, char next) throws IOException {
        ch = before;
        in.unread(next);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy