///|
pub struct Lexer {
  input : String
  mut pos : Int
  mut line : Int
  mut col : Int
}

///|
fn get_char_at(s : String, i : Int) -> Char {
  match s[i].to_int().to_char() {
    Some(c) => c
    None => ' '
  }
}

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

///|
pub fn Lexer::next_token(self : Lexer) -> Token {
  self.skip_whitespace()
  if self.pos >= self.input.length() {
    return Eof
  }
  let ch = get_char_at(self.input, self.pos)
  if ch == '#' {
    self.skip_comment()
    return self.next_token()
  }
  if ch == ':' {
    self.advance()
    return Colon
  }
  if ch == '=' {
    self.advance()
    return Equal
  }
  if ch == '|' {
    self.advance()
    if self.pos < self.input.length() &&
      get_char_at(self.input, self.pos) == '|' {
      self.advance()
      return PipePipe
    }
    return Pipe
  }
  if ch == '\n' {
    self.advance_line()
    return Newline
  }
  self.parse_ident()
}

///|
fn Lexer::advance(self : Lexer) -> Unit {
  self.pos += 1
  self.col += 1
}

///|
fn Lexer::advance_line(self : Lexer) -> Unit {
  self.pos += 1
  self.line += 1
  self.col = 1
}

///|
fn Lexer::skip_whitespace(self : Lexer) -> Unit {
  while self.pos < self.input.length() {
    let ch = get_char_at(self.input, self.pos)
    if ch == ' ' || ch == '\t' || ch == '\r' {
      self.advance()
    } else {
      break
    }
  }
}

///|
fn Lexer::skip_comment(self : Lexer) -> Unit {
  while self.pos < self.input.length() {
    let ch = get_char_at(self.input, self.pos)
    if ch == '\n' {
      break
    }
    self.advance()
  }
}

///|
fn Lexer::parse_ident(self : Lexer) -> Token {
  let start = self.pos
  while self.pos < self.input.length() {
    let ch = get_char_at(self.input, self.pos)
    if (ch >= 'a' && ch <= 'z') ||
      (ch >= 'A' && ch <= 'Z') ||
      (ch >= '0' && ch <= '9') ||
      ch == '_' ||
      ch == '-' ||
      ch == '.' ||
      ch == '$' ||
      ch == '/' {
      self.advance()
    } else {
      break
    }
  }
  if start == self.pos {
    let err_ch = get_char_at(self.input, self.pos)
    self.advance()
    return Error("Unexpected character: \{err_ch}")
  }
  Ident(self.input[start:self.pos].to_owned())
}

///|
pub fn Lexer::read_rest_of_line(self : Lexer) -> String {
  self.skip_whitespace()
  let start = self.pos
  while self.pos < self.input.length() {
    let ch = get_char_at(self.input, self.pos)
    if ch == '\n' {
      break
    }
    self.advance()
  }
  self.input[start:self.pos].to_owned()
}