001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.util.struct.parser;
006
007/** Raw struct schema lexer. */
008public class Lexer {
009  /**
010   * Construct a raw struct schema lexer.
011   *
012   * @param in schema
013   */
014  public Lexer(String in) {
015    m_in = in;
016  }
017
018  /**
019   * Gets the next token.
020   *
021   * @return Token kind; the token text can be retrieved using getTokenText()
022   */
023  public TokenKind scan() {
024    // skip whitespace
025    do {
026      get();
027    } while (m_current == ' ' || m_current == '\t' || m_current == '\n' || m_current == '\r');
028    m_tokenStart = m_pos - 1;
029
030    return switch (m_current) {
031      case '[' -> TokenKind.kLeftBracket;
032      case ']' -> TokenKind.kRightBracket;
033      case '{' -> TokenKind.kLeftBrace;
034      case '}' -> TokenKind.kRightBrace;
035      case ':' -> TokenKind.kColon;
036      case ';' -> TokenKind.kSemicolon;
037      case ',' -> TokenKind.kComma;
038      case '=' -> TokenKind.kEquals;
039      case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> scanInteger();
040      case '\0' -> TokenKind.kEndOfInput;
041      default -> {
042        if (Character.isLetter(m_current) || m_current == '_') {
043          yield scanIdentifier();
044        }
045        yield TokenKind.kUnknown;
046      }
047    };
048  }
049
050  /**
051   * Gets the text of the last lexed token.
052   *
053   * @return token text
054   */
055  public String getTokenText() {
056    if (m_tokenStart >= m_in.length()) {
057      return "";
058    }
059    return m_in.substring(m_tokenStart, m_pos);
060  }
061
062  /**
063   * Gets the starting position of the last lexed token.
064   *
065   * @return position (0 = first character)
066   */
067  public int getPosition() {
068    return m_tokenStart;
069  }
070
071  private TokenKind scanInteger() {
072    do {
073      get();
074    } while (Character.isDigit(m_current));
075    unget();
076    return TokenKind.kInteger;
077  }
078
079  private TokenKind scanIdentifier() {
080    do {
081      get();
082    } while (Character.isLetterOrDigit(m_current) || m_current == '_');
083    unget();
084    return TokenKind.kIdentifier;
085  }
086
087  private void get() {
088    if (m_pos < m_in.length()) {
089      m_current = m_in.charAt(m_pos);
090    } else {
091      m_current = '\0';
092    }
093    ++m_pos;
094  }
095
096  private void unget() {
097    if (m_pos > 0) {
098      m_pos--;
099      if (m_pos < m_in.length()) {
100        m_current = m_in.charAt(m_pos);
101      } else {
102        m_current = '\0';
103      }
104    } else {
105      m_current = '\0';
106    }
107  }
108
109  final String m_in;
110  char m_current;
111  int m_tokenStart;
112  int m_pos;
113}