#[cfg(test)]

use lex;
use lex::Token;
use lex::Token_Type;
use lex::lex_str;

fn format_token(tok: &Token) -> String {
  match tok.kind {
    Token_Type::Space(ref _val) => format!("Space!"),
    Token_Type::Comment(ref val) => format!("Comment! '{}'", val),
    Token_Type::Subtract => format!("Subtract!"),
    Token_Type::Long_Comment(ref val, ref len) => format!("Long_Comment: '{}' {}", val, len),
    Token_Type::Int(ref val) => format!("Int {}", val),
    Token_Type::Float(ref val) => format!("Float {}", val),
    Token_Type::Error(ref val) => format!("Error: '{}'", val),
    Token_Type::Str(ref val, ref del) => format!("String: '{}' '{}'", val, del),
    _ => format!("Something!"),
  }
}

fn cmp_tokens(left: &Token, right: &Token) -> bool {
  match left.kind {
    Token_Type::None => { match right.kind { Token_Type::None => true, _ => false}},
    Token_Type::Function => { match right.kind { Token_Type::Function => true, _ => false}},
    Token_Type::And => { match right.kind { Token_Type::And => true, _ => false}},
    Token_Type::Or => { match right.kind { Token_Type::Or => true, _ => false}},
    Token_Type::Xor => { match right.kind { Token_Type::Xor => true, _ => false}},
    Token_Type::Not => { match right.kind { Token_Type::Not => true, _ => false}},
    Token_Type::Do => { match right.kind { Token_Type::Do => true, _ => false}},
    Token_Type::End => { match right.kind { Token_Type::End => true, _ => false}},
    Token_Type::Return => { match right.kind { Token_Type::Return => true, _ => false}},
    Token_Type::Break => { match right.kind { Token_Type::Break => true, _ => false}},
    Token_Type::Goto => { match right.kind { Token_Type::Goto => true, _ => false}},
    Token_Type::If => { match right.kind { Token_Type::If => true, _ => false}},
    Token_Type::Then => { match right.kind { Token_Type::Then => true, _ => false}},
    Token_Type::Else => { match right.kind { Token_Type::Else => true, _ => false}},
    Token_Type::Else_If => { match right.kind { Token_Type::Else_If => true, _ => false}},
    Token_Type::For => { match right.kind { Token_Type::For => true, _ => false}},
    Token_Type::While => { match right.kind { Token_Type::While => true, _ => false}},
    Token_Type::Repeat => { match right.kind { Token_Type::Repeat => true, _ => false}},
    Token_Type::Until => { match right.kind { Token_Type::Until => true, _ => false}},
    Token_Type::Nil => { match right.kind { Token_Type::Nil => true, _ => false}},
    Token_Type::False => { match right.kind { Token_Type::False => true, _ => false}},
    Token_Type::True => { match right.kind { Token_Type::True => true, _ => false}},
    Token_Type::Method_Call => { match right.kind { Token_Type::Method_Call => true, _ => false}},
    Token_Type::Label_Marker => { match right.kind { Token_Type::Label_Marker => true, _ => false}},
    Token_Type::Member => { match right.kind { Token_Type::Member => true, _ => false}},
    Token_Type::Concat => { match right.kind { Token_Type::Concat => true, _ => false}},
    Token_Type::Ellipsis => { match right.kind { Token_Type::Ellipsis => true, _ => false}},
    Token_Type::Equal => { match right.kind { Token_Type::Equal => true, _ => false}},
    Token_Type::Greater => { match right.kind { Token_Type::Greater => true, _ => false}},
    Token_Type::Greater_Equal => { match right.kind { Token_Type::Greater_Equal => true, _ => false}},
    Token_Type::Lesser => { match right.kind { Token_Type::Lesser => true, _ => false}},
    Token_Type::Lesser_Equal => { match right.kind { Token_Type::Lesser_Equal => true, _ => false}},
    Token_Type::Not_Equal => { match right.kind { Token_Type::Not_Equal => true, _ => false}},
    Token_Type::Assign => { match right.kind { Token_Type::Assign => true, _ => false}},
    Token_Type::Add => { match right.kind { Token_Type::Add => true, _ => false}},
    Token_Type::Subtract => { match right.kind { Token_Type::Subtract => true, _ => false}},
    Token_Type::Divide => { match right.kind { Token_Type::Divide => true, _ => false}},
    Token_Type::Integer_Divide => { match right.kind { Token_Type::Integer_Divide => true, _ => false}},
    Token_Type::Multiply => { match right.kind { Token_Type::Multiply => true, _ => false}},
    Token_Type::Exponent => { match right.kind { Token_Type::Exponent => true, _ => false}},
    Token_Type::Modulus => { match right.kind { Token_Type::Modulus => true, _ => false}},
    Token_Type::Length => { match right.kind { Token_Type::Length => true, _ => false}},
    Token_Type::Shift_Left => { match right.kind { Token_Type::Shift_Left => true, _ => false}},
    Token_Type::Shift_Right => { match right.kind { Token_Type::Shift_Right => true, _ => false}},
    Token_Type::Bitwise_And => { match right.kind { Token_Type::Bitwise_And => true, _ => false}},
    Token_Type::Bitwise_Or => { match right.kind { Token_Type::Bitwise_Or => true, _ => false}},
    Token_Type::Bitwise_Not_Or_Xor => { match right.kind { Token_Type::Bitwise_Not_Or_Xor => true, _ => false}},
    Token_Type::Int(ref left_val) => { match right.kind { Token_Type::Int(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::Float(ref left_val) => { match right.kind { Token_Type::Float(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::Str(ref left_val, ref _delim) => { match right.kind { Token_Type::Str(ref right_val, ref _delim) => left_val == right_val, _ => false}},
    Token_Type::Long_Str(ref left_val, ref left_count) => { match right.kind { Token_Type::Long_Str(ref right_val, ref right_count) => left_val == right_val && left_count == right_count, _ => false}},
    Token_Type::Local => { match right.kind { Token_Type::Local => true, _ => false}},
    Token_Type::Name(ref left_val) => { match right.kind { Token_Type::Name(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::Element_Separator => { match right.kind { Token_Type::Element_Separator => true, _ => false}},
    Token_Type::Table_Open => { match right.kind { Token_Type::Table_Open => true, _ => false}},
    Token_Type::Table_Close => { match right.kind { Token_Type::Table_Close => true, _ => false}},
    Token_Type::Index_Open => { match right.kind { Token_Type::Index_Open => true, _ => false}},
    Token_Type::Index_Close => { match right.kind { Token_Type::Index_Close => true, _ => false}},
    Token_Type::Paren_Open => { match right.kind { Token_Type::Paren_Open => true, _ => false}},
    Token_Type::Paren_Close => { match right.kind { Token_Type::Paren_Close => true, _ => false}},
    Token_Type::Comment(ref left_val) => { match right.kind { Token_Type::Comment(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::Long_Comment(ref left_val, ref left_count) => { match right.kind { Token_Type::Long_Comment(ref right_val, ref right_count) => left_val == right_val && left_count == right_count, _ => false}},
    Token_Type::Space(ref left_val) => { match right.kind { Token_Type::Space(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::New_Line(ref left_val) => { match right.kind { Token_Type::New_Line(ref right_val) => left_val == right_val, _ => false}},
    Token_Type::Error(ref left_val) => { match right.kind { Token_Type::Error(ref right_val) => left_val == right_val, _ => false}},
  }
}

pub fn test_lex(input: &str, result: Vec<Token>) -> bool {
  let tokens = lex_str(input);
  let num_tokens = if tokens.len() > result.len() {tokens.len()} else {result.len()};
  let mut fail_reasons: Vec<String> = Vec::new();
  for id in 0..num_tokens {
    if id >= tokens.len() {
      fail_reasons.push(format!("tokens too short"));
      if id >= result.len() {
        fail_reasons.push(format!("and result too short"));
      } else {
        fail_reasons.push(format_token(&result[id]));
      }
    } else {
      if id >= result.len() {
        fail_reasons.push(format!("result too short"));
        fail_reasons.push(format_token(&tokens[id]));
      } else {
        if cmp_tokens(&tokens[id], &result[id]) {
        } else {
          fail_reasons.push(format!("Token_Types at {} do not match", id));
          fail_reasons.push(format_token(&tokens[id]));
          fail_reasons.push(format_token(&result[id]));
        }
      }
    }
  }
  if fail_reasons.len() == 0 {
    true
  } else {
    println!("lex test: '{}' failed", input);
    for reason in fail_reasons.iter() {
      println!("{}", reason);
    }
    false
  }
}

fn new_token(kind: Token_Type) -> Token {
  Token{line_id: 0, char_id: 0, kind: kind}
}

#[test]
pub fn lex_comment() {
  assert!(test_lex("  --  \n  -- lorem ipsum with spaces after    \n  -- bazorg\n -", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Comment("--  ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::New_Line("\n".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Comment("-- lorem ipsum with spaces after    ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::New_Line("\n".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Comment("-- bazorg".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::New_Line("\n".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Subtract})));
}
#[test]
pub fn lex_long_comment() {
  assert!(test_lex("--[[foo]] --[=[bar]=] --[===[dash]==]]===]\n--[[foo\nbar]]", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Comment("foo".to_string(), 0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Comment("bar".to_string(), 1)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Comment("dash]==]".to_string(), 3)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::New_Line("\n".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Comment("foo\nbar".to_string(), 0)},
  )));
}
#[test]
pub fn lex_long_string() {
  assert!(test_lex("[[foo]] [=[bar]=] [===[dash]==]]===] [[\nfoo]] [[\nfoo\n]] [[\nfoo\nbar]]", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("foo".to_string(), 0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("bar".to_string(), 1)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("dash]==]".to_string(), 3)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("foo".to_string(), 0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("foo\n".to_string(), 0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Str("foo\nbar".to_string(), 0)},
  )));
}
#[test]
pub fn lex_index_ops() {
  assert!(test_lex("[] [--[[foo]]] [ ]", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Open},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Close},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Open},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Long_Comment("foo".to_string(), 0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Close},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Open},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Index_Close},
  )));
}
#[test]
pub fn lex_comparisons() {
  assert!(test_lex("= == === ==== <=<>=>>=", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Assign},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Assign},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Lesser_Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Lesser},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Greater_Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Greater},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Greater_Equal},
  )));
}
#[test]
pub fn lex_slash_colon() {
  assert!(test_lex("//// /~ ~= ~~= =~ ::: :=~", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Integer_Divide},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Integer_Divide},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Divide},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_Not_Or_Xor},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Not_Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_Not_Or_Xor},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Not_Equal},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Assign},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_Not_Or_Xor},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Label_Marker},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Method_Call},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Method_Call},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Assign},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_Not_Or_Xor},
  )));
}
#[test]
pub fn lex_string() {
  assert!(test_lex("\"foo\"'foo''foo\\nbar'", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Str("foo".to_string(), '"')},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Str("foo".to_string(), '\'')},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Str("foo\nbar".to_string(), '\'')},
  )));
}
#[test]
pub fn lex_dots() {
  assert!(test_lex("...... ..... .... ... .. .", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Ellipsis},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Ellipsis},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Ellipsis},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Concat},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Ellipsis},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Member},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Ellipsis},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Concat},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Member},
  )));
}
#[test]
pub fn lex_numeral() {
  assert!(test_lex("0 1 12345 12.345 .123 0x123", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Int(0)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Int(1)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Int(12345)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Float(12.345)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Float(0.123)},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Int(0x123)},
  )));
}
#[test]
pub fn lex_singles() {
  assert!(test_lex("+*%&^|#,;(){}", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Add},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Multiply},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Modulus},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_And},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Exponent},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Bitwise_Or},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Length},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Element_Separator},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Element_Separator},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Paren_Open},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Paren_Close},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Table_Open},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Table_Close},
  )));
}
#[test]
pub fn lex_names() {
  assert!(test_lex("function and or not do end return break goto if then else elseif for while repeat until nil false true local foo _ _123 foo123 function2", vec!(
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Function},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::And},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Or},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Not},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Do},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::End},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Return},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Break},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Goto},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::If},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Then},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Else},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Else_If},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::For},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::While},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Repeat},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Until},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Nil},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::False},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::True},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Local},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Name("foo".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Name("_".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Name("_123".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Name("foo123".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Space(" ".to_string())},
    Token{line_id: 0, char_id: 0, kind:
          Token_Type::Name("function2".to_string())},
  )));
}

use parse::Binary_Op;
use parse::Unary_Op;
use parse::Expression;
use parse::parse_expression;
use parse::binop_name;
use parse::unop_name;

fn expr_name(left: &Expression) -> String {
  match *left {
    Expression::None => "none".to_string(),
    Expression::Tok(_) => "tok".to_string(),
    Expression::Binop(_, _, _) => "binop".to_string(),
    Expression::Unop(_, _) => "unop".to_string(),
    Expression::Error(_, _) => "error".to_string(),
  }
}

fn cmp_sub_expr(left: Expression, right: Expression, mut fail_reasons: &mut Vec<String>) -> bool {
  let left_name = expr_name(&left);
  match left {
    Expression::None => match right {
      Expression::None => true,
      _ => {
        fail_reasons.push(format!("left  {} != right {}", left_name, expr_name(&right)));
        false
      }
    },
    Expression::Tok(left_tok) => match right {
      Expression::Tok(right_tok) => {
        if cmp_tokens(&left_tok, &right_tok) {true} else {
          fail_reasons.push(format!("left tok {} != right tok {}", format_token(&left_tok), format_token(&right_tok)));
          false
        }
      },
      _ => {
        fail_reasons.push(format!("left  {} != right {}", left_name, expr_name(&right)));
        false
      }
    },
    Expression::Binop(left_op, left_first, left_second) => {
      let lop_name = binop_name(&left_op);
      let lf_name = expr_name(&left_first);
      let ls_name = expr_name(&left_second);
      match right {
        Expression::Binop(right_op, right_first, right_second) => {
          let rop_name = binop_name(&right_op);
          let rf_name = expr_name(&right_first);
          let rs_name = expr_name(&right_second);
          if left_op == right_op {
            if cmp_sub_expr(*left_first, *right_first, &mut fail_reasons) {
              if cmp_sub_expr(*left_second, *right_second, &mut fail_reasons) {
                true
              } else {
                fail_reasons.push(format!("second args {} != {}", ls_name, rs_name));
                false
              }
            } else {
              fail_reasons.push(format!("first args {} != {}", lf_name, rf_name));
              false
            }
          } else {
            fail_reasons.push(format!("operators {} != {}", lop_name, rop_name));
            false
          }
        },
        _ => {
          fail_reasons.push(format!("left  {} != right {}", left_name, expr_name(&right)));
          false
        }
      }
    },
    Expression::Unop(left_op, left_first) => {
      let lop_name = unop_name(&left_op);
      let lf_name = expr_name(&left_first);
      match right {
        Expression::Unop(right_op, right_first) => {
          let rop_name = unop_name(&right_op);
          let rf_name = expr_name(&right_first);
          if left_op == right_op {
            if cmp_sub_expr(*left_first, *right_first, &mut fail_reasons) {
              true
            } else {
              fail_reasons.push(format!("unary args {} != {}", lf_name, rf_name));
              false
            }
          } else {
            fail_reasons.push(format!("operators {} != {}", lop_name, rop_name));
            false
          }
        },
        _ => {
          fail_reasons.push(format!("left  {} != right {}", left_name, expr_name(&right)));
          false
        }
      }
    },
    Expression::Error(left_tok, left_why) => match right {
      Expression::Error(right_tok, right_why) => {
        if cmp_tokens(&left_tok, &right_tok) {
          if left_why == right_why {true} else {
            fail_reasons.push(format!("error reasons {} != {}", left_why, right_why));
            false
          }
        } else {
          fail_reasons.push(format!("error tokens {} != {}", format_token(&left_tok), format_token(&right_tok)));
          false
        }
      },
      _ => {
        fail_reasons.push(format!("left  {} != right {}", left_name, expr_name(&right)));
        false
      }
    }
  }
}

fn test_parse(input: &str, result: Expression, dump: bool) -> bool {
  let tokens = lex_str(input);
  let expr = parse_expression(tokens, dump);
  let mut fail_reasons: Vec<String> = Vec::new();
  if cmp_sub_expr(expr, result, &mut fail_reasons) {true} else {
    println!("parse test: '{}' failed", input);
    for reason in fail_reasons.iter() {
      println!("{}", reason);
    }
    false
  }
}

fn int_expr(val: i64) -> Expression {
  Expression::Tok(Token{line_id: 0, char_id: 0, kind: Token_Type::Int(val)})
}
fn float_expr(val: f64) -> Expression {
  Expression::Tok(Token{line_id: 0, char_id: 0, kind: Token_Type::Float(val)})
}

#[test]
pub fn parse_trivial() {
  assert!(
    test_parse(
      "1", int_expr(1)
        , false));
  assert!(
    test_parse(
      "-1", Expression::Unop(Unary_Op::Negative, Box::new(int_expr(1)))
        , false));
  assert!(
    test_parse(
      "1+1",
      Expression::Binop(Binary_Op::Add, Box::new(int_expr(1)), Box::new(int_expr(1)))
        , false));
  assert!(
    test_parse(
      "1+1*2",
      Expression::Binop(Binary_Op::Add, Box::new(int_expr(1)), Box::new(
        Expression::Binop(Binary_Op::Multiply, Box::new(int_expr(1)), Box::new(int_expr(2)))))
        , false));
}
#[test]
pub fn lex_semitriv() {
  assert!(
    test_lex(
      "1.1^1.2^1.3^-1.4^1.5^1.6", vec!(
        new_token(Token_Type::Float(1.1)),
        new_token(Token_Type::Exponent),
        new_token(Token_Type::Float(1.2)),
        new_token(Token_Type::Exponent),
        new_token(Token_Type::Float(1.3)),
        new_token(Token_Type::Exponent),
        new_token(Token_Type::Subtract),
        new_token(Token_Type::Float(1.4)),
        new_token(Token_Type::Exponent),
        new_token(Token_Type::Float(1.5)),
        new_token(Token_Type::Exponent),
        new_token(Token_Type::Float(1.6)),
      )));
}
#[test]
pub fn parse_semitriv() {
  assert!(
    test_parse(
      "1.1^1.2^1.3^-1.4^1.5^1.6",
      Expression::Binop(
        Binary_Op::Exponent, Box::new(float_expr(1.1)),
        Box::new(
          Expression::Binop(
            Binary_Op::Exponent,
            Box::new(float_expr(1.2)),
            Box::new(
              Expression::Binop(
                Binary_Op::Exponent,
                Box::new(float_expr(1.3)),
                Box::new(
                  Expression::Unop(
                    Unary_Op::Negative,
                    Box::new(
                      Expression::Binop(
                        Binary_Op::Exponent,
                        Box::new(float_expr(1.4)),
                        Box::new(
                          Expression::Binop(
                            Binary_Op::Exponent,
                            Box::new(float_expr(1.5)),
                            Box::new(float_expr(1.6))
                          ))
                      ))
                  ))
              ))
          ))
      )
        , true));
}
#[test]
pub fn parse_erroneous() {
  assert!(
    test_parse(
      "^-^",
      Expression::Binop(
        Binary_Op::Exponent,
        Box::new(
          Expression::Error(
            Token::new_blank(), "oper_requires_left_value".to_string())),
        Box::new(
          Expression::Binop(
            Binary_Op::Exponent,
            Box::new(
              Expression::Unop(
                Unary_Op::Negative,
                Box::new(
                  Expression::Error(
                    Token::new_blank(), "oper_requires_right_value".to_string()))
              )),
            Box::new(
              Expression::Error(
                Token::new_blank(), "oper_requires_right_value".to_string()))
          ))
      )
        , true));
}
