///| Lexer for expr_lang

///|
/// Token produced by the lexer
pub struct Token {
  kind : @cst.SyntaxKind
  text : String
} derive(Debug, Eq)

///|
pub fn Token::new(kind : @cst.SyntaxKind, text : String) -> Token {
  Token::{ kind, text }
}

///|
pub fn Token::kind(self : Token) -> @cst.SyntaxKind {
  self.kind
}

///|
pub fn Token::text(self : Token) -> String {
  self.text
}

///|
/// Lexer state
pub struct Lexer {
  input : String
  mut pos : Int
} derive(Debug)

///|
pub fn Lexer::new(input : String) -> Lexer {
  Lexer::{ input, pos: 0 }
}

///|
fn Lexer::peek(self : Lexer) -> Char? {
  if self.pos >= self.input.length() {
    None
  } else {
    Some(self.input[self.pos].to_int().unsafe_to_char())
  }
}

///|
fn Lexer::advance(self : Lexer) -> Char? {
  match self.peek() {
    Some(c) => {
      self.pos += 1
      Some(c)
    }
    None => None
  }
}

///|
fn is_alpha(c : Char) -> Bool {
  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}

///|
fn is_digit(c : Char) -> Bool {
  c >= '0' && c <= '9'
}

///|
fn is_alnum(c : Char) -> Bool {
  is_alpha(c) || is_digit(c)
}

///|
fn is_whitespace_char(c : Char) -> Bool {
  c == ' ' || c == '\t'
}

///|
fn Lexer::slice_from(self : Lexer, start : Int) -> String {
  let buf = StringBuilder()
  for i = start; i < self.pos; i = i + 1 {
    buf.write_char(self.input[i].to_int().unsafe_to_char())
  }
  buf.to_string()
}

///|
/// Lex a single token
pub fn Lexer::next_token(self : Lexer) -> Token {
  match self.peek() {
    None => Token::new(eof(), "")
    Some(c) => {
      // Whitespace (not newline)
      if is_whitespace_char(c) {
        let start = self.pos
        while (match self.peek() {
                Some(ch) => is_whitespace_char(ch)
                None => false
              }) {
          let _ = self.advance()
        }
        let text = self.slice_from(start)
        return Token::new(whitespace(), text)
      }
      // Newline
      if c == '\n' {
        let _ = self.advance()
        return Token::new(newline(), "\n")
      }
      // Identifiers and keywords
      if is_alpha(c) {
        let start = self.pos
        while (match self.peek() {
                Some(ch) => is_alnum(ch)
                None => false
              }) {
          let _ = self.advance()
        }
        let text = self.slice_from(start)
        let kind = match text {
          "let" => let_kw()
          "fn" => fn_kw()
          _ => name()
        }
        return Token::new(kind, text)
      }
      // Numbers
      if is_digit(c) {
        let start = self.pos
        while (match self.peek() {
                Some(ch) => is_digit(ch)
                None => false
              }) {
          let _ = self.advance()
        }
        let text = self.slice_from(start)
        return Token::new(number(), text)
      }
      // Single character tokens
      let _ = self.advance()
      match c {
        '=' => Token::new(eq(), "=")
        '+' => Token::new(plus(), "+")
        '-' => Token::new(minus(), "-")
        '*' => Token::new(star(), "*")
        '/' => Token::new(slash(), "/")
        '(' => Token::new(lparen(), "(")
        ')' => Token::new(rparen(), ")")
        '{' => Token::new(lbrace(), "{")
        '}' => Token::new(rbrace(), "}")
        ',' => Token::new(comma(), ",")
        _ => Token::new(error_kind(), c.to_string())
      }
    }
  }
}

///|
/// Check if lexer is at end
pub fn Lexer::is_eof(self : Lexer) -> Bool {
  self.pos >= self.input.length()
}

///|
/// Tokenize entire input
pub fn tokenize(input : String) -> Array[Token] {
  let lexer = Lexer::new(input)
  let tokens : Array[Token] = []
  while !lexer.is_eof() {
    tokens.push(lexer.next_token())
  }
  tokens.push(Token::new(eof(), ""))
  tokens
}