#![cfg(test)]
use std::collections::HashSet;
use serde_json;
use serde_json::Value;
use unjson::*;
use unjson::error::*;
use unjson::result::Result;
use unjson::ty::*;
use token::*;
use word::{Reserved, Name};
use context::{Context, Mode};
use std;
macro_rules! right {
( $l:expr, $r:expr ) => { $r }
}
macro_rules! tuplify {
( $v:expr, ( $($dummy:tt),* ) ) => {
{
let mut t = $v.into_iter();
($(
right!($dummy, t.next().unwrap())
),*)
}
};
}
pub struct LexerTest {
pub source: String,
pub context: Context,
pub expected: std::result::Result<TokenData, String>
}
pub trait IntoTest {
fn into_lexer_test(self) -> Result<LexerTest>;
}
pub trait IntoTestSuite {
fn into_lexer_test_suite(self) -> Result<Vec<LexerTest>>;
}
impl IntoTestSuite for Array {
fn into_lexer_test_suite(self) -> Result<Vec<LexerTest>> {
let mut result = Vec::with_capacity(self.len());
for data in self {
result.push(try!(data.into_lexer_test()));
}
Ok(result)
}
}
impl IntoTestSuite for Value {
fn into_lexer_test_suite(self) -> Result<Vec<LexerTest>> {
self.into_array().and_then(|arr| arr.into_lexer_test_suite())
}
}
impl IntoTest for Object {
fn into_lexer_test(mut self) -> Result<LexerTest> {
let source = try!(self.extract_string("source"));
let set = try!(self.extract_array("context").and_then(|arr| arr.into_string_set()));
let expected = if self.contains_key("error") {
Err(try!(self.extract_string("error")))
} else {
Ok(try!(self.extract_field("expected").and_then(|data| data.into_token())))
};
let mut context = Context::new(Mode::Sloppy);
context.operator = set.contains("operator");
Ok(LexerTest {
source: source,
context: context,
expected: expected
})
}
}
impl IntoTest for Value {
fn into_lexer_test(self) -> Result<LexerTest> {
self.into_object().and_then(|obj| obj.into_lexer_test())
}
}
trait IntoStringSet {
fn into_string_set(self) -> Result<HashSet<String>>;
}
impl IntoStringSet for Array {
fn into_string_set(self) -> Result<HashSet<String>> {
let mut set = HashSet::new();
for data in self {
set.insert(try!(data.into_string()));
}
Ok(set)
}
}
pub trait IntoName {
fn into_name(self) -> Result<Name>;
}
impl IntoName for Value {
fn into_name(self) -> Result<Name> {
Ok(Name::from(try!(self.into_string())))
}
}
pub trait IntoReserved {
fn into_reserved(self) -> Result<Reserved>;
}
impl IntoReserved for String {
fn into_reserved(self) -> Result<Reserved> {
Ok(match &self[..] {
"Null" => Reserved::Null,
"True" => Reserved::True,
"False" => Reserved::False,
"Break" => Reserved::Break,
"Case" => Reserved::Case,
"Catch" => Reserved::Catch,
"Class" => Reserved::Class,
"Const" => Reserved::Const,
"Continue" => Reserved::Continue,
"Debugger" => Reserved::Debugger,
"Default" => Reserved::Default,
"Delete" => Reserved::Delete,
"Do" => Reserved::Do,
"Else" => Reserved::Else,
"Export" => Reserved::Export,
"Extends" => Reserved::Extends,
"Finally" => Reserved::Finally,
"For" => Reserved::For,
"Function" => Reserved::Function,
"If" => Reserved::If,
"Import" => Reserved::Import,
"In" => Reserved::In,
"Instanceof" => Reserved::Instanceof,
"New" => Reserved::New,
"Return" => Reserved::Return,
"Super" => Reserved::Super,
"Switch" => Reserved::Switch,
"This" => Reserved::This,
"Throw" => Reserved::Throw,
"Try" => Reserved::Try,
"Typeof" => Reserved::Typeof,
"Var" => Reserved::Var,
"Void" => Reserved::Void,
"While" => Reserved::While,
"With" => Reserved::With,
"Enum" => Reserved::Enum,
_ => { return string_error("keyword", self); }
})
}
}
pub trait IntoToken {
fn into_char_case(self) -> Result<CharCase>;
fn into_char_case_opt(self) -> Result<Option<CharCase>>;
fn into_exp_opt(self) -> Result<Option<Exp>>;
fn into_token(self) -> Result<TokenData>;
}
fn validate_token(arr: Array) -> Result<Array> {
if arr.len() == 0 {
return index_error(0, arr.len());
}
let expected_len = {
let elt = arr.iter().next().unwrap();
let ty = match elt.as_string() {
None => { return type_error("string", elt.ty()); }
Some(str) => str
};
match ty {
"Reserved" => 2,
"DecimalInt" => 3,
"BinaryInt" => 3,
"OctalInt" => 3,
"HexInt" => 3,
"Float" => 4,
"String" => 2,
"RegExp" => 3,
"Identifier" => 2,
_ => 1
}
};
let actual_len = arr.len();
if actual_len != expected_len {
return array_error(expected_len, actual_len);
}
Ok(arr)
}
impl IntoToken for Value {
fn into_char_case(self) -> Result<CharCase> {
let s = try!(self.into_string());
if s.len() == 0 {
return string_error("lowercase or uppercase letter", s);
}
let ch = s.chars().next().unwrap();
if ch.is_lowercase() {
Ok(CharCase::LowerCase)
} else if ch.is_uppercase() {
Ok(CharCase::UpperCase)
} else {
string_error("lowercase or uppercase letter", s)
}
}
fn into_char_case_opt(self) -> Result<Option<CharCase>> {
match self {
Value::Null => Ok(None),
_ => self.into_char_case().map(Some)
}
}
fn into_exp_opt(self) -> Result<Option<Exp>> {
match self {
Value::Null => Ok(None),
_ => {
let arr = try!(self.into_array());
if arr.len() != 3 {
return array_error(3, arr.len());
}
let (e, sign, value) = tuplify!(arr, ((), (), ()));
Ok(Some(Exp {
e: try!(e.into_char_case()),
sign: match try!(sign.into_string_opt()) {
None => None,
Some(s) => {
if s.len() != 1 {
return string_error("'+' or '-'", s);
}
match s.chars().next().unwrap() {
'+' => Some(Sign::Plus),
'-' => Some(Sign::Minus),
_ => { return string_error("'+' or '-'", s); }
}
}
},
value: try!(value.into_string())
}))
}
}
}
fn into_token(self) -> Result<TokenData> {
let mut arr = try!(self.into_array());
arr = try!(validate_token(arr));
let ty = try!(arr.remove(0).into_string());
Ok(match &ty[..] {
"Reserved" => {
let word = try!(arr.remove(0).into_string().and_then(|str| str.into_reserved()));
TokenData::Reserved(word)
}
"LBrace" => TokenData::LBrace,
"RBrace" => TokenData::RBrace,
"LParen" => TokenData::LParen,
"RParen" => TokenData::RParen,
"LBrack" => TokenData::LBrack,
"RBrack" => TokenData::RBrack,
"Dot" => TokenData::Dot,
"Semi" => TokenData::Semi,
"Comma" => TokenData::Comma,
"LAngle" => TokenData::LAngle,
"RAngle" => TokenData::RAngle,
"LEq" => TokenData::LEq,
"GEq" => TokenData::GEq,
"Eq" => TokenData::Eq,
"NEq" => TokenData::NEq,
"StrictEq" => TokenData::StrictEq,
"StrictNEq" => TokenData::StrictNEq,
"Plus" => TokenData::Plus,
"Minus" => TokenData::Minus,
"Star" => TokenData::Star,
"Mod" => TokenData::Mod,
"Slash" => TokenData::Slash,
"Inc" => TokenData::Inc,
"Dec" => TokenData::Dec,
"LShift" => TokenData::LShift,
"RShift" => TokenData::RShift,
"URShift" => TokenData::URShift,
"BitAnd" => TokenData::BitAnd,
"BitOr" => TokenData::BitOr,
"BitXor" => TokenData::BitXor,
"Bang" => TokenData::Bang,
"Tilde" => TokenData::Tilde,
"LogicalAnd" => TokenData::LogicalAnd,
"LogicalOr" => TokenData::LogicalOr,
"Question" => TokenData::Question,
"Colon" => TokenData::Colon,
"Assign" => TokenData::Assign,
"PlusAssign" => TokenData::PlusAssign,
"MinusAssign" => TokenData::MinusAssign,
"StarAssign" => TokenData::StarAssign,
"SlashAssign" => TokenData::SlashAssign,
"ModAssign" => TokenData::ModAssign,
"LShiftAssign" => TokenData::LShiftAssign,
"RShiftAssign" => TokenData::RShiftAssign,
"URShiftAssign" => TokenData::URShiftAssign,
"BitAndAssign" => TokenData::BitAndAssign,
"BitOrAssign" => TokenData::BitOrAssign,
"BitXorAssign" => TokenData::BitXorAssign,
"Arrow" => TokenData::Arrow,
"EOF" => TokenData::EOF,
"DecimalInt" => {
let (value, exp) = tuplify!(arr, ((), ()));
let value = try!(value.into_string());
let exp = try!(exp.into_exp_opt());
NumberSource::DecimalInt(value, exp).into_token_data()
}
"BinaryInt" => {
let (flag, value) = tuplify!(arr, ((), ()));
let flag = try!(flag.into_char_case());
let value = try!(value.into_string());
NumberSource::RadixInt(Radix::Bin(flag), value).into_token_data()
}
"OctalInt" => {
let (flag, value) = tuplify!(arr, ((), ()));
let flag = try!(flag.into_char_case_opt());
let value = try!(value.into_string());
NumberSource::RadixInt(Radix::Oct(flag), value).into_token_data()
}
"HexInt" => {
let (flag, value) = tuplify!(arr, ((), ()));
let flag = try!(flag.into_char_case());
let value = try!(value.into_string());
NumberSource::RadixInt(Radix::Hex(flag), value).into_token_data()
}
"Float" => {
let (int, frac, exp) = tuplify!(arr, ((), (), ()));
let int = try!(int.into_string_opt());
let frac = try!(frac.into_string_opt());
let exp = try!(exp.into_exp_opt());
NumberSource::Float(int, frac, exp).into_token_data()
}
"String" => {
let value = try!(arr.remove(0).into_string());
TokenData::String(StringLiteral {
source: format!("{:?}", Value::String(value.to_string())),
value: value
})
}
"RegExp" => {
let (pattern, flags) = tuplify!(arr, ((), ()));
let pattern = try!(pattern.into_string());
let flags = try!(flags.into_string()).chars().collect();
TokenData::RegExp(RegExpLiteral {
pattern: pattern,
flags: flags
})
}
"Identifier" => TokenData::Identifier(try!(arr.remove(0).into_name())),
_ => { return type_error("token", Ty::Array); }
})
}
}
pub fn deserialize_lexer_tests(src: &str) -> Vec<LexerTest> {
let data: Value = match serde_json::from_str(src) {
Ok(data) => data,
Err(err) => panic!(format!("invalid JSON: {:?}", err))
};
match data.into_lexer_test_suite() {
Ok(result) => result,
Err(err) => panic!(format!("lexer test failed to deserialize: {}", err))
}
}