diff --git a/.github/workflows/code-format.yaml b/.github/workflows/code-format.yaml new file mode 100644 index 0000000..5fb5633 --- /dev/null +++ b/.github/workflows/code-format.yaml @@ -0,0 +1,29 @@ +name: Linting + +permissions: + contents: read + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Formatter + run: rustup component add rustfmt + + - name: Run Composite Build Action + uses: ./.github/actions/build + + - name: Run Format Test + run: cargo fmt --all -- --check \ No newline at end of file diff --git a/.github/workflows/doc-tests.yaml b/.github/workflows/doc-tests.yaml index 76e06f4..e15687e 100644 --- a/.github/workflows/doc-tests.yaml +++ b/.github/workflows/doc-tests.yaml @@ -1,4 +1,7 @@ -name: Document Tests +name: Document + +permissions: + contents: read on: push: @@ -10,7 +13,7 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test: runs-on: ubuntu-latest steps: - name: Checkout Repository diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 2b57ddf..0db6d55 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -1,4 +1,7 @@ -name: Integration Tests +name: Integration + +permissions: + contents: read on: push: @@ -10,7 +13,7 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test: runs-on: ubuntu-latest steps: - name: Checkout Repository diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index c4ad040..a8643a6 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -1,4 +1,7 @@ -name: Unit Tests +name: Unit + +permissions: + contents: read on: push: @@ -10,7 +13,7 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test: runs-on: ubuntu-latest steps: - name: Checkout Repository diff --git a/README.md b/README.md index c3389a4..3382043 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ + +

**EAQL (English Augmented Query Language)** is a simplified, English-like query language designed as natural-language alternative to SQL. It's built for learners, educators, and tinkerers. diff --git a/src/bin/repl.rs b/src/bin/repl.rs index d28a894..2a3a4a4 100644 --- a/src/bin/repl.rs +++ b/src/bin/repl.rs @@ -1,12 +1,8 @@ -use eaql::{ - transpiler, - validator, - utils, -}; +use eaql::{transpiler, utils, validator}; fn main() { let args: Vec = std::env::args().collect(); - + utils::help::display_logo(); if args.len() < 2 { @@ -17,7 +13,8 @@ fn main() { match args[args.len() - 1].as_str() { "transpile" => transpiler::repl_loop(), "query_test" => validator::repl_loop(), - _ => utils::help::display_help( - Some(format!("Invalid Testing CLI Argument -> {}, see usage!", args[2]).as_str())) + _ => utils::help::display_help(Some( + format!("Invalid Testing CLI Argument -> {}, see usage!", args[2]).as_str(), + )), } -} \ No newline at end of file +} diff --git a/src/language/lexer.rs b/src/language/lexer.rs index 42cc811..d47110c 100644 --- a/src/language/lexer.rs +++ b/src/language/lexer.rs @@ -1,28 +1,17 @@ -use std::fmt; -use crate::{ - language::{ - tokens::{ - Token, - TokenType, - IDENTIFER_STOPS, - SINGLE_DOUBLE_START_TOKENS, - SINGLE_START_TOKENS, - SYSTEM_KEYWORDS - } - } +use crate::language::tokens::{ + IDENTIFER_STOPS, SINGLE_DOUBLE_START_TOKENS, SINGLE_START_TOKENS, SYSTEM_KEYWORDS, Token, + TokenType, }; +use std::fmt; #[derive(Debug)] pub struct Lexer { - pub tokens: Vec + pub tokens: Vec, } impl Lexer { // Look ahead for one token - fn peek_one( - query: &String, - current: &usize - ) -> Option { + fn peek_one(query: &String, current: &usize) -> Option { if current + 1 >= query.len() { return None; } @@ -30,18 +19,14 @@ impl Lexer { return query.chars().nth(*current + 1); } - fn peek_decimal( - query: &String, - start: &mut usize, - current: &mut usize, - ) { + fn peek_decimal(query: &String, start: &mut usize, current: &mut usize) { loop { if *current >= query.len() { // Fix edge case where query ends with decimal - // (i.e. 2. becomes a number literal but should be a number and eoq token) + // (i.e. 2. becomes a number literal but should be a number and eoq token) if *current > 0 { let prev = query.as_bytes()[*current - 1]; - + if matches!(prev, b'.' | b'!' | b';') { *current -= 1; } @@ -51,7 +36,7 @@ impl Lexer { } *current += 1; - + let slice = &query.as_bytes()[*start..*current]; let s = match std::str::from_utf8(slice) { Ok(s) => s, @@ -60,7 +45,7 @@ impl Lexer { return; } }; - + if s.parse::().is_err() { *current -= 1; return; @@ -68,11 +53,7 @@ impl Lexer { } } - fn peek_string( - query: &String, - current: &mut usize, - token_type: &mut TokenType - ) -> () { + fn peek_string(query: &String, current: &mut usize, token_type: &mut TokenType) -> () { loop { if *current + 1 >= query.len() { *token_type = TokenType::UnknownToken; @@ -80,7 +61,7 @@ impl Lexer { } *current += 1; - + if query.chars().nth(*current).unwrap() == '\"' { return; } @@ -98,8 +79,7 @@ impl Lexer { return; } - if IDENTIFER_STOPS.contains( - &query.chars().nth(*current).unwrap()) { + if IDENTIFER_STOPS.contains(&query.chars().nth(*current).unwrap()) { let tmp = &query[*start..*current]; if let Some(keyword_token) = SYSTEM_KEYWORDS.get(tmp.to_lowercase().as_str()) { @@ -113,46 +93,37 @@ impl Lexer { } } - fn handle_single_token( - _query: &String, - c: char, - current: &mut usize - ) -> Result { + fn handle_single_token(_query: &String, c: char, current: &mut usize) -> Result { let token_type: TokenType = match c { n if [';', '!', '.'].contains(&n) => TokenType::EoqToken, ')' => TokenType::CloseParen, '(' => TokenType::OpenParen, ',' => TokenType::Comma, - _ => TokenType::UnknownToken + _ => TokenType::UnknownToken, }; *current += 1; - return Ok(Token::new( - token_type, - &"".to_string(), - &c.to_string() - )); + return Ok(Token::new(token_type, &"".to_string(), &c.to_string())); } - /* Handle slightly complex tokens like '>' which might be + /* Handle slightly complex tokens like '>' which might be succeeded by '=' */ fn handle_single_double_token( query: &String, c: char, - current: &mut usize + current: &mut usize, ) -> Result { let slice_start = *current; let peeked_token: Option = Lexer::peek_one(query, current); - let token_type: TokenType = - if c == '>' { + let token_type: TokenType = if c == '>' { if peeked_token == Some('=') { *current += 2; TokenType::Gte - } else { + } else { *current += 1; TokenType::Gt - } + } } else if c == '<' { if peeked_token == Some('=') { *current += 2; @@ -173,7 +144,7 @@ impl Lexer { return Ok(Token::new( token_type, &"".to_string(), - &query[slice_start..slice_end].to_string() + &query[slice_start..slice_end].to_string(), )); } @@ -181,62 +152,51 @@ impl Lexer { query: &String, c: char, current: &mut usize, - start: &mut usize + start: &mut usize, ) -> Result { let mut token_type: TokenType; let literal: String; let slice_start: usize = *current; - if c == ' ' { // Whitespace + if c == ' ' { + // Whitespace token_type = TokenType::WhitespaceToken; literal = " ".to_string() - } else if c.is_digit(10) || - ( - c == '-' && - Lexer::peek_one(query, ¤t).is_some_and(|x: char| x.is_digit(10)) - ) { // Number literals + } else if c.is_digit(10) + || (c == '-' && Lexer::peek_one(query, ¤t).is_some_and(|x: char| x.is_digit(10))) + { + // Number literals token_type = TokenType::NumberLiteral; - if c == '-' && - Lexer::peek_one(query, ¤t).is_some_and(|x: char| x.is_digit(10)) { + if c == '-' && Lexer::peek_one(query, ¤t).is_some_and(|x: char| x.is_digit(10)) { *current += 1; } - Lexer::peek_decimal( - query, - start, - current); - + Lexer::peek_decimal(query, start, current); + literal = query[*start..*current].to_string(); *current -= 1; } else if c == '\"' { token_type = TokenType::StringLiteral; - Lexer::peek_string( - query, - current, - &mut token_type - ); + Lexer::peek_string(query, current, &mut token_type); if token_type == TokenType::UnknownToken { *current += 1; // We weren't able to increment in the loop return Ok(Token::new( token_type, &"".to_string(), - &query[slice_start..*current].to_string() + &query[slice_start..*current].to_string(), )); } - + literal = query[*start + 1..*current].to_string(); - } else { // This is where we handle an identifier, or keyword + } else { + // This is where we handle an identifier, or keyword token_type = TokenType::Identifier; - Lexer::peek_identifier( - query, - start, - current, - &mut token_type); - + Lexer::peek_identifier(query, start, current, &mut token_type); + // Keywords don't need literals literal = if token_type == TokenType::Identifier { query[*start..*current].to_string() @@ -253,33 +213,19 @@ impl Lexer { return Ok(Token::new( token_type, &literal, - &query[slice_start..slice_end].to_string() + &query[slice_start..slice_end].to_string(), )); } - fn next_token( - query: &String, - current: &mut usize, - start: &mut usize, - ) -> Result { + fn next_token(query: &String, current: &mut usize, start: &mut usize) -> Result { let c: char = query.chars().nth(*current).unwrap(); if SINGLE_START_TOKENS.contains(&c) { - return Lexer::handle_single_token( - query, - c, - current); + return Lexer::handle_single_token(query, c, current); } else if SINGLE_DOUBLE_START_TOKENS.contains(&c) { - return Lexer::handle_single_double_token( - query, - c, - current); + return Lexer::handle_single_double_token(query, c, current); } else { - return Lexer::handle_default( - query, - c, - current, - start) + return Lexer::handle_default(query, c, current, start); } } @@ -290,11 +236,8 @@ impl Lexer { let mut current: usize = 0; while current < query.len() { - let token: Result = Lexer::next_token( - query, - &mut current, - &mut start); - + let token: Result = Lexer::next_token(query, &mut current, &mut start); + if token.is_err() { warnings.push(token.err().unwrap()); } else { @@ -307,28 +250,25 @@ impl Lexer { Ok(Lexer { tokens: toks .into_iter() - .filter(|x: &Token| x.token_type != TokenType::WhitespaceToken && - x.token_type != TokenType::NullToken - ) - .collect() + .filter(|x: &Token| { + x.token_type != TokenType::WhitespaceToken + && x.token_type != TokenType::NullToken + }) + .collect(), }) } } impl fmt::Display for Lexer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Lexer {{ Tokens: {:#?} }}", - self.tokens) + write!(f, "Lexer {{ Tokens: {:#?} }}", self.tokens) } } -pub fn scan_tokens(query: &String) -> Result{ +pub fn scan_tokens(query: &String) -> Result { Lexer::new(query) } - // Begin Lexer Tests #[cfg(test)] @@ -343,36 +283,12 @@ mod tests { assert!(!test_lexer.is_err()); let expected: Vec = vec![ - Token::new( - TokenType::OpenParen, - &"".to_string(), - &"(".to_string(), - ), - Token::new( - TokenType::CloseParen, - &"".to_string(), - &")".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &".".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &";".to_string(), - ), - Token::new( - TokenType::Comma, - &"".to_string(), - &",".to_string(), - ), + Token::new(TokenType::OpenParen, &"".to_string(), &"(".to_string()), + Token::new(TokenType::CloseParen, &"".to_string(), &")".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &".".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &";".to_string()), + Token::new(TokenType::Comma, &"".to_string(), &",".to_string()), ]; assert_eq!(expected, test_lexer.unwrap().tokens); @@ -382,35 +298,15 @@ mod tests { fn unit_test_basic_single_double_tokens() { let input: String = "<><=>==".to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); let expected: Vec = vec![ - Token::new( - TokenType::Lt, - &"".to_string(), - &"<".to_string(), - ), - Token::new( - TokenType::Gt, - &"".to_string(), - &">".to_string(), - ), - Token::new( - TokenType::Lte, - &"".to_string(), - &"<=".to_string(), - ), - Token::new( - TokenType::Gte, - &"".to_string(), - &">=".to_string(), - ), - Token::new( - TokenType::Equal, - &"".to_string(), - &"=".to_string(), - ), + Token::new(TokenType::Lt, &"".to_string(), &"<".to_string()), + Token::new(TokenType::Gt, &"".to_string(), &">".to_string()), + Token::new(TokenType::Lte, &"".to_string(), &"<=".to_string()), + Token::new(TokenType::Gte, &"".to_string(), &">=".to_string()), + Token::new(TokenType::Equal, &"".to_string(), &"=".to_string()), ]; assert_eq!(expected, test_lexer.unwrap().tokens); @@ -423,13 +319,11 @@ mod tests { assert!(!test_lexer.is_err()); - let expected: Vec = vec![ - Token::new( - TokenType::StringLiteral, - &"Hi1234".to_string(), - &"\"Hi1234\"".to_string(), - ), - ]; + let expected: Vec = vec![Token::new( + TokenType::StringLiteral, + &"Hi1234".to_string(), + &"\"Hi1234\"".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -441,13 +335,11 @@ mod tests { assert!(!test_lexer.is_err()); - let expected: Vec = vec![ - Token::new( - TokenType::UnknownToken, - &"".to_string(), - &"\"Hi1234".to_string(), - ), - ]; + let expected: Vec = vec![Token::new( + TokenType::UnknownToken, + &"".to_string(), + &"\"Hi1234".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -456,16 +348,14 @@ mod tests { fn unit_test_basic_string_literal_error_2() { let input: String = "\"".to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); - - let expected: Vec = vec![ - Token::new( - TokenType::UnknownToken, - &"".to_string(), - &"\"".to_string(), - ), - ]; + + let expected: Vec = vec![Token::new( + TokenType::UnknownToken, + &"".to_string(), + &"\"".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -477,13 +367,11 @@ mod tests { assert!(!test_lexer.is_err()); - let expected: Vec = vec![ - Token::new( - TokenType::StringLiteral, - &"".to_string(), - &"\"\"".to_string(), - ), - ]; + let expected: Vec = vec![Token::new( + TokenType::StringLiteral, + &"".to_string(), + &"\"\"".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -495,13 +383,11 @@ mod tests { assert!(!test_lexer.is_err()); - let expected: Vec = vec![ - Token::new( - TokenType::NumberLiteral, - &"1234".to_string(), - &"1234".to_string(), - ), - ]; + let expected: Vec = vec![Token::new( + TokenType::NumberLiteral, + &"1234".to_string(), + &"1234".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -510,20 +396,16 @@ mod tests { fn unit_test_edge_number_literal_decimal() { let input: String = "12.".to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); - + let expected: Vec = vec![ Token::new( TokenType::NumberLiteral, &"12".to_string(), &"12".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &".".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &".".to_string()), ]; assert_eq!(expected, test_lexer.unwrap().tokens); @@ -533,16 +415,14 @@ mod tests { fn unit_test_basic_number_literal_decimal() { let input: String = "12.34".to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); - - let expected: Vec = vec![ - Token::new( - TokenType::NumberLiteral, - &"12.34".to_string(), - &"12.34".to_string(), - ), - ]; + + let expected: Vec = vec![Token::new( + TokenType::NumberLiteral, + &"12.34".to_string(), + &"12.34".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -554,13 +434,11 @@ mod tests { assert!(!test_lexer.is_err()); - let expected: Vec = vec![ - Token::new( - TokenType::NumberLiteral, - &"-12.34".to_string(), - &"-12.34".to_string(), - ), - ]; + let expected: Vec = vec![Token::new( + TokenType::NumberLiteral, + &"-12.34".to_string(), + &"-12.34".to_string(), + )]; assert_eq!(expected, test_lexer.unwrap().tokens); } @@ -569,35 +447,23 @@ mod tests { fn unit_test_basic_keyword_literal_1() { let input: String = "get all from place.".to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); let expected: Vec = vec![ - Token::new( - TokenType::Get, - &"".to_string(), - &"get".to_string(), - ), + Token::new(TokenType::Get, &"".to_string(), &"get".to_string()), Token::new( TokenType::WildcardKeyword, &"".to_string(), &"all".to_string(), ), - Token::new( - TokenType::From, - &"".to_string(), - &"from".to_string(), - ), + Token::new(TokenType::From, &"".to_string(), &"from".to_string()), Token::new( TokenType::Identifier, &"place".to_string(), &"place".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &".".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &".".to_string()), ]; assert_eq!(expected, test_lexer.unwrap().tokens); @@ -605,27 +471,21 @@ mod tests { #[test] fn unit_test_basic_keyword_literal_2() { - let input: String = "retrieve everything from place whenever name is \"Coffee\" and cost >= 2.43!".to_string(); + let input: String = + "retrieve everything from place whenever name is \"Coffee\" and cost >= 2.43!" + .to_string(); let test_lexer: Result = Lexer::new(&input); - + assert!(!test_lexer.is_err()); let expected: Vec = vec![ - Token::new( - TokenType::Get, - &"".to_string(), - &"retrieve".to_string(), - ), + Token::new(TokenType::Get, &"".to_string(), &"retrieve".to_string()), Token::new( TokenType::WildcardKeyword, &"".to_string(), &"everything".to_string(), ), - Token::new( - TokenType::From, - &"".to_string(), - &"from".to_string(), - ), + Token::new(TokenType::From, &"".to_string(), &"from".to_string()), Token::new( TokenType::Identifier, &"place".to_string(), @@ -641,43 +501,27 @@ mod tests { &"name".to_string(), &"name".to_string(), ), - Token::new( - TokenType::Equal, - &"".to_string(), - &"is".to_string(), - ), + Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), Token::new( TokenType::StringLiteral, &"Coffee".to_string(), &"\"Coffee\"".to_string(), ), - Token::new( - TokenType::And, - &"".to_string(), - &"and".to_string() - ), + Token::new(TokenType::And, &"".to_string(), &"and".to_string()), Token::new( TokenType::Identifier, &"cost".to_string(), - &"cost".to_string() - ), - Token::new( - TokenType::Gte, - &"".to_string(), - &">=".to_string() + &"cost".to_string(), ), + Token::new(TokenType::Gte, &"".to_string(), &">=".to_string()), Token::new( TokenType::NumberLiteral, &"2.43".to_string(), &"2.43".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; assert_eq!(expected, test_lexer.unwrap().tokens); } -} \ No newline at end of file +} diff --git a/src/language/mod.rs b/src/language/mod.rs index 7111d2b..113ec24 100644 --- a/src/language/mod.rs +++ b/src/language/mod.rs @@ -1,3 +1,3 @@ +pub mod lexer; pub mod parser; pub mod tokens; -pub mod lexer; \ No newline at end of file diff --git a/src/language/parser/conditional.rs b/src/language/parser/conditional.rs index aad9058..15b91c0 100644 --- a/src/language/parser/conditional.rs +++ b/src/language/parser/conditional.rs @@ -1,14 +1,11 @@ use std::fmt; + use crate::{ language::{ - parser::helpers::{ - get_tab, valid_until_warning, validate_length - }, - tokens::{ - Token, TokenType - } + parser::helpers::{get_tab, valid_until_warning, validate_length}, + tokens::{Token, TokenType}, }, - utils::logger + utils::logger, }; #[derive(Debug, PartialEq)] @@ -23,7 +20,7 @@ pub struct ConditionNode { pub enum ConditionChild { Op(Box), Expr(Box), - Bool(Box) + Bool(Box), } #[derive(Debug, PartialEq)] @@ -33,7 +30,7 @@ pub struct OperandNode { _ls: ConditionChild, _rs: ConditionChild, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] @@ -42,26 +39,24 @@ pub struct ExpressionNode { _comparison_operator: Token, _literal: Token, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] pub struct BoolNode { _value: bool, - _depth: u16 + _depth: u16, } -fn update_depths( - node: &mut ConditionChild -) -> () { +fn update_depths(node: &mut ConditionChild) -> () { match node { ConditionChild::Op(state) => { state._depth += 1; update_depths(&mut state._ls); update_depths(&mut state._rs); return; - }, + } ConditionChild::Bool(state) => state._depth += 1, ConditionChild::Expr(state) => state._depth += 1, } @@ -77,32 +72,34 @@ fn handle_open_paren( closing_or: &mut bool, ) -> Result { let mut ret: ConditionChild = ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), - _depth: depth, - _ls: match recurse_down( - tokens, - idx, - depth + 1, - "OR".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - }, - _rs: match recurse_down( - tokens, - idx, - depth + 1, - "OR".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - } + _type: "OR".to_string(), + _depth: depth, + _ls: match recurse_down( + tokens, + idx, + depth + 1, + "OR".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + _rs: match recurse_down( + tokens, + idx, + depth + 1, + "OR".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, })); match tokens[*idx].token_type { @@ -116,27 +113,26 @@ fn handle_open_paren( update_depths(&mut ret); } - return Ok(ConditionChild::Op(Box::new( - OperandNode { - _type: "AND".to_string(), - _ls: ret, - _rs: match recurse_down( - tokens, - idx, - depth + 1, - "AND".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - }, - - _depth: depth - } - ))); - }, + return Ok(ConditionChild::Op(Box::new(OperandNode { + _type: "AND".to_string(), + _ls: ret, + _rs: match recurse_down( + tokens, + idx, + depth + 1, + "AND".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + + _depth: depth, + }))); + } TokenType::Or => { *idx += 1; *closing_paren = false; @@ -146,53 +142,59 @@ fn handle_open_paren( if logger::LOG_LEVEL <= logger::DEBUG.0 { update_depths(&mut ret); } - - return Ok(ConditionChild::Op(Box::new( - OperandNode { - _type: "OR".to_string(), - _ls: ret, - _rs: match recurse_down( - tokens, - idx, - depth + 1, - "OR".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - }, - - _depth: depth - } - ))); - }, + + return Ok(ConditionChild::Op(Box::new(OperandNode { + _type: "OR".to_string(), + _ls: ret, + _rs: match recurse_down( + tokens, + idx, + depth + 1, + "OR".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + + _depth: depth, + }))); + } TokenType::CloseParen => { if *opened_paren == 0 { - return Err("Closing parentheses found with unmatched opening in conditional!".to_string()); + return Err( + "Closing parentheses found with unmatched opening in conditional!".to_string(), + ); } *opened_paren -= 1; *idx += 1; - + return Ok(ret); - }, + } TokenType::PostProcessorEntrance => { *closing_paren = false; *finished = true; - + return Ok(ret); - }, + } TokenType::EoqToken => { *closing_paren = false; *finished = true; return Ok(ret); - }, - _ => return Err(format!("Something went wrong parsing a nested conditional: Expected a closing parentheses, \ + } + _ => { + return Err(format!( + "Something went wrong parsing a nested conditional: Expected a closing parentheses, \ post-processor entrance, end-of-query, 'and' or 'or', \ - but got '{}' instead.", tokens[*idx].lexeme)) + but got '{}' instead.", + tokens[*idx].lexeme + )); + } } } @@ -206,32 +208,34 @@ fn handle_and( closing_or: &mut bool, ) -> Result { Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "AND".to_string(), - _depth: depth, - _ls: match recurse_down( - tokens, - idx, - depth + 1, - "AND".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - }, - _rs: match recurse_down( - tokens, - idx, - depth + 1, - "AND".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - } + _type: "AND".to_string(), + _depth: depth, + _ls: match recurse_down( + tokens, + idx, + depth + 1, + "AND".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + _rs: match recurse_down( + tokens, + idx, + depth + 1, + "AND".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, }))) } @@ -244,56 +248,50 @@ fn handle_literal( opened_paren: &mut u16, closing_or: &mut bool, ) -> Result { - let ls: ConditionChild = ConditionChild::Expr( - Box::new(match ExpressionNode::parse(tokens, idx, depth + 1) { + let ls: ConditionChild = ConditionChild::Expr(Box::new( + match ExpressionNode::parse(tokens, idx, depth + 1) { Ok(node) => node, - Err(msg) => return Err(msg) - }) - ); + Err(msg) => return Err(msg), + }, + )); let rs: ConditionChild = match recurse_down( - tokens, - idx, - depth + 1, - "AND".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { + tokens, + idx, + depth + 1, + "AND".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { Ok(node) => node, - Err(msg) => return Err(msg) + Err(msg) => return Err(msg), }; - return Ok( - ConditionChild::Op(Box::new(OperandNode { - _type: "AND".to_string(), - _depth: depth, - _ls: ls, - _rs: rs - })) - ); + return Ok(ConditionChild::Op(Box::new(OperandNode { + _type: "AND".to_string(), + _depth: depth, + _ls: ls, + _rs: rs, + }))); } -fn handle_close( - parent_node: &String, - depth: u16 -) -> ConditionChild { +fn handle_close(parent_node: &String, depth: u16) -> ConditionChild { // AND default to true, OR defaults to false if *parent_node == "AND" { - ConditionChild::Bool(Box::new( - BoolNode { _value: true, _depth: depth } - )) + ConditionChild::Bool(Box::new(BoolNode { + _value: true, + _depth: depth, + })) } else { - ConditionChild::Bool(Box::new( - BoolNode { _value: false, _depth: depth } - )) + ConditionChild::Bool(Box::new(BoolNode { + _value: false, + _depth: depth, + })) } } -fn handle_or( - closing_or: &mut bool, - parent_node: &String, - depth: u16, -) -> ConditionChild { +fn handle_or(closing_or: &mut bool, parent_node: &String, depth: u16) -> ConditionChild { *closing_or = true; handle_close(&parent_node, depth) } @@ -320,54 +318,84 @@ fn parse_child( match tokens[*idx].token_type { TokenType::And => { *idx += 1; - return handle_and(tokens, idx, depth, finished, closing_paren, opened_paren, closing_or); - }, + return handle_and( + tokens, + idx, + depth, + finished, + closing_paren, + opened_paren, + closing_or, + ); + } TokenType::Or => { *idx += 1; - return Ok(handle_or(closing_or, &parent_node, depth)) - }, + return Ok(handle_or(closing_or, &parent_node, depth)); + } TokenType::OpenParen => { *idx += 1; *opened_paren += 1; - return handle_open_paren(tokens, idx, depth, finished, closing_paren, opened_paren, closing_or); - }, + return handle_open_paren( + tokens, + idx, + depth, + finished, + closing_paren, + opened_paren, + closing_or, + ); + } TokenType::CloseParen => { *idx += 1; if *opened_paren == 0 { - return Err("Closing parentheses found with unmatched opening in conditional!".to_string()); + return Err( + "Closing parentheses found with unmatched opening in conditional!".to_string(), + ); } *opened_paren -= 1; - return Ok(handle_close_paren(closing_paren, &parent_node, depth)) - }, + return Ok(handle_close_paren(closing_paren, &parent_node, depth)); + } TokenType::PostProcessorEntrance => { if *closing_paren { - return Err("Found end of conditional, but there are unclosed parentheses!".to_string()) + return Err( + "Found end of conditional, but there are unclosed parentheses!".to_string(), + ); } *finished = true; - return Ok(handle_close(&parent_node, depth)) - }, + return Ok(handle_close(&parent_node, depth)); + } TokenType::EoqToken => { if *closing_paren { - return Err("Found end of conditional, but there are unclosed parentheses!".to_string()) + return Err( + "Found end of conditional, but there are unclosed parentheses!".to_string(), + ); } *finished = true; - return Ok(handle_close(&parent_node, depth)) - }, + return Ok(handle_close(&parent_node, depth)); + } TokenType::Identifier => { - return handle_literal(tokens, idx, depth, finished, closing_paren, opened_paren, closing_or); - }, + return handle_literal( + tokens, + idx, + depth, + finished, + closing_paren, + opened_paren, + closing_or, + ); + } _ => { return Err(format!( "Unexpected token found while parsing conditional expression -> {}", tokens[*idx].lexeme - )) - } + )); + } } } @@ -396,52 +424,107 @@ fn recurse_down( if parent_node == "OR" { *closing_or = false; - return Ok( - ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), - _depth: depth, - _ls: match recurse_down( - tokens, - idx, - depth + 1, - "OR".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - }, - _rs: match recurse_down( - tokens, - idx, - depth + 1, - "OR".to_string(), - finished, - closing_paren, - opened_paren, - closing_or) { - Ok(node) => node, - Err(msg) => return Err(msg) - } - })) - ); + return Ok(ConditionChild::Op(Box::new(OperandNode { + _type: "OR".to_string(), + _depth: depth, + _ls: match recurse_down( + tokens, + idx, + depth + 1, + "OR".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + _rs: match recurse_down( + tokens, + idx, + depth + 1, + "OR".to_string(), + finished, + closing_paren, + opened_paren, + closing_or, + ) { + Ok(node) => node, + Err(msg) => return Err(msg), + }, + }))); } return Ok(handle_close(&parent_node, depth)); } - return parse_child(tokens, idx, depth, parent_node, finished, closing_paren, opened_paren, closing_or); + return parse_child( + tokens, + idx, + depth, + parent_node, + finished, + closing_paren, + opened_paren, + closing_or, + ); } impl ConditionNode { + /// Reconstructs conditional literal from provided tokens + /// and bounds. + fn reconstruct_literal(tokens: &Vec, start_idx: usize, end_idx: usize) -> String { + // Reconstruct literal + let mut i: usize = start_idx; + let mut literal = String::new(); + + while i < end_idx { + match tokens[i].token_type { + TokenType::OpenParen | TokenType::CloseParen => { + literal.push_str(&tokens[i].lexeme); + i += 1; + } + TokenType::And | TokenType::Or => { + literal.push_str(&format!(" {} ", &tokens[i].lexeme)); + i += 1; + } + TokenType::Identifier => { + let mut j = i; + let mut parts = Vec::new(); + + while parts.len() < 3 { + parts.push(if tokens[j].token_type == TokenType::Equal { + "=".to_string() + } else { + tokens[j].lexeme.clone() + }); + j += 1; + } + + if !parts.is_empty() { + literal.push_str(&parts.join(" ")); + } + + i = j; + } + _ => { + literal.push_str(&tokens[i].lexeme); + i += 1; + } + } + } + + literal + } + /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. pub fn parse( tokens: &Vec, idx: &mut usize, - depth: u16 + depth: u16, ) -> Result { let mut finished: bool = false; let mut closing_paren: bool = false; @@ -453,67 +536,54 @@ impl ConditionNode { _type: "OR".to_string(), _depth: depth + 1, _ls: match recurse_down( - tokens, - idx, - depth + 2, - "OR".to_string(), - &mut finished, - &mut closing_paren, - &mut opened_paren, - &mut closing_or) { + tokens, + idx, + depth + 2, + "OR".to_string(), + &mut finished, + &mut closing_paren, + &mut opened_paren, + &mut closing_or, + ) { Ok(node) => node, - Err(msg) => return Err(msg) + Err(msg) => return Err(msg), }, _rs: match recurse_down( - tokens, - idx, - depth + 2, - "OR".to_string(), - &mut finished, - &mut closing_paren, - &mut opened_paren, - &mut closing_or) { + tokens, + idx, + depth + 2, + "OR".to_string(), + &mut finished, + &mut closing_paren, + &mut opened_paren, + &mut closing_or, + ) { Ok(node) => node, - Err(msg) => return Err(msg) - } + Err(msg) => return Err(msg), + }, })); - + if opened_paren != 0 { return Err("Conditional had unclosed parentheses".to_string()); } - return Ok( - ConditionNode { - _condition: ret, - _depth: depth, - _literal: { - tokens[start_idx..*idx].iter() - .map(|v| if v.token_type == TokenType::Equal { - "=" - } else { - v.lexeme.as_str() - }) - .collect::>() - .join(" ") - } - } - ) + return Ok(ConditionNode { + _condition: ret, + _depth: depth, + _literal: ConditionNode::reconstruct_literal(tokens, start_idx, *idx), + }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> String { - format!("{}", self._literal) + pub fn transpile_color(&self) -> String { + format!("{}", self._literal) } /// Outputs current AST node transpiled to raw SQL - pub fn transpile_raw( - &self - ) -> String { - format!("{}", self._literal) + pub fn transpile_raw(&self) -> String { + format!("{}", self._literal) } } @@ -524,7 +594,8 @@ impl ExpressionNode { pub fn parse( tokens: &Vec, idx: &mut usize, - depth: u16) -> Result { + depth: u16, + ) -> Result { validate_length(tokens, &(*idx + 2), true)?; let identifier: Token; @@ -537,36 +608,38 @@ impl ExpressionNode { } else { return Err(valid_until_warning(tokens, idx)); } - + if vec![ TokenType::Equal, TokenType::Lte, TokenType::Lt, TokenType::Gt, - TokenType::Gte - ].contains(&tokens[*idx].token_type) { + TokenType::Gte, + ] + .contains(&tokens[*idx].token_type) + { comparison_operator = tokens[*idx].clone(); *idx += 1; } else { return Err(valid_until_warning(tokens, idx)); } - + if tokens[*idx].token_type == TokenType::StringLiteral - || tokens[*idx].token_type == TokenType::NumberLiteral { + || tokens[*idx].token_type == TokenType::NumberLiteral + { literal = tokens[*idx].clone(); *idx += 1; } else { return Err(valid_until_warning(tokens, idx)); } - + return Ok(ExpressionNode { _identifier: identifier, _comparison_operator: comparison_operator, _literal: literal, - _depth: depth - } - ) + _depth: depth, + }); } } @@ -585,7 +658,7 @@ impl fmt::Display for ConditionNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(Condition){}{}", + "\n{}(Condition){}{}", get_tab(self._depth), get_tab(self._depth + 1), self._condition @@ -597,7 +670,7 @@ impl fmt::Display for OperandNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(Operand::{}){}{}", + "\n{}(Operand::{}){}{}", get_tab(self._depth), self._type, self._ls, @@ -608,12 +681,7 @@ impl fmt::Display for OperandNode { impl fmt::Display for BoolNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, -"\n{}(Bool::{})", - get_tab(self._depth), - self._value, - ) + write!(f, "\n{}(Bool::{})", get_tab(self._depth), self._value,) } } @@ -621,7 +689,7 @@ impl fmt::Display for ExpressionNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(Expression) + "\n{}(Expression) {}variable: {:?} {}operator: {:?} {}value: {:?}", @@ -636,7 +704,7 @@ impl fmt::Display for ExpressionNode { } } -// Begin Conditional Tests +// Begin Conditional Tests #[cfg(test)] mod tests { use super::*; @@ -644,85 +712,43 @@ mod tests { #[test] fn unit_test_expression_parsing_normal() { let input: Vec = vec![ - Token::new( - TokenType::Identifier, - &"".to_string(), - &"id".to_string(), - ), - Token::new( - TokenType::Equal, - &"".to_string(), - &"is".to_string(), - ), - Token::new( - TokenType::NumberLiteral, - &"5".to_string(), - &"5".to_string(), - ) + Token::new(TokenType::Identifier, &"".to_string(), &"id".to_string()), + Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), + Token::new(TokenType::NumberLiteral, &"5".to_string(), &"5".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; let expected: ExpressionNode = ExpressionNode { - _identifier: Token::new( - TokenType::Identifier, - &"".to_string(), - &"id".to_string(), - ), - _comparison_operator: Token::new( - TokenType::Equal, - &"".to_string(), - &"is".to_string(), - ), - _literal: Token::new( - TokenType::NumberLiteral, - &"5".to_string(), - &"5".to_string(), - ), - - _depth: 0 + _identifier: Token::new(TokenType::Identifier, &"".to_string(), &"id".to_string()), + _comparison_operator: Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), + _literal: Token::new(TokenType::NumberLiteral, &"5".to_string(), &"5".to_string()), + + _depth: 0, }; - match ExpressionNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => { - assert_eq!(expected, val) - }, - Err(err) => assert!(false, "Output errored out -> {}", err) + match ExpressionNode::parse(&input, &mut idx, depth) { + Ok(val) => { + assert_eq!(expected, val) + } + Err(err) => assert!(false, "Output errored out -> {}", err), } } #[test] fn unit_test_expression_parsing_error() { let input: Vec = vec![ - Token::new( - TokenType::Identifier, - &"".to_string(), - &"id".to_string(), - ), - Token::new( - TokenType::Equal, - &"".to_string(), - &"is".to_string(), - ), - Token::new( - TokenType::Get, - &"".to_string(), - &"get".to_string(), - ) + Token::new(TokenType::Identifier, &"".to_string(), &"id".to_string()), + Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), + Token::new(TokenType::Get, &"".to_string(), &"get".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; - match ExpressionNode::parse( - &input, - &mut idx, - depth) { - Ok(_val) => assert!(false, "Output expected to error!"), - Err(err) => assert!(true, "Output errored out -> {}", err) + match ExpressionNode::parse(&input, &mut idx, depth) { + Ok(_val) => assert!(false, "Output expected to error!"), + Err(err) => assert!(true, "Output errored out -> {}", err), } } -} \ No newline at end of file +} diff --git a/src/language/parser/database.rs b/src/language/parser/database.rs index 5b3a588..d230c72 100644 --- a/src/language/parser/database.rs +++ b/src/language/parser/database.rs @@ -5,50 +5,43 @@ This handles our database mutators and accessors Make the database db_name. */ - -use std::{fmt, usize}; use crate::{ language::{ parser::{ - helpers::{ - get_tab, validate_length, peek_one - }, + helpers::{get_tab, peek_one, validate_length}, parser::ImpliedAction, - }, tokens::{ - Token, TokenType - } + }, + tokens::{Token, TokenType}, }, utils::{ - colors::{ - AnsiColor, - colorize, - }, - logger - } + colors::{AnsiColor, colorize}, + logger, + }, }; +use std::{fmt, usize}; #[derive(Debug)] -pub struct DatabaseNode { +pub struct DatabaseNode { pub _create: Option, pub _destroy: Option, pub _use: Option, pub _show: Option, _literal: String, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] pub struct ShowNode { - _depth: u16 -} + _depth: u16, +} #[derive(Debug, PartialEq)] pub struct DestroyNode { databases: Vec, _literal: String, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] @@ -56,7 +49,7 @@ pub struct CreateNode { name: String, _literal: String, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] @@ -64,22 +57,18 @@ pub struct UseNode { name: String, _literal: String, - _depth: u16 + _depth: u16, } impl DatabaseNode { - pub fn parse( + pub fn parse( tokens: &Vec, idx: &mut usize, depth: u16, action: ImpliedAction, - ) -> Result{ - validate_length( - tokens , - idx, - true)?; + ) -> Result { + validate_length(tokens, idx, true)?; - let mut database_node: DatabaseNode = DatabaseNode { _create: None, _destroy: None, @@ -87,60 +76,53 @@ impl DatabaseNode { _show: None, _literal: { - tokens[*idx - 2..*idx].iter() + tokens[*idx - 2..*idx] + .iter() .map(|v| v.lexeme.as_str()) .collect::>() .join(" ") - }, - _depth: depth + }, + _depth: depth, }; match action { ImpliedAction::Create => { - database_node._create = match CreateNode::parse( - tokens, - idx, - depth + 1 - ) { + database_node._create = match CreateNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), - Err(msg) => return Err(msg) - }}, + Err(msg) => return Err(msg), + } + } ImpliedAction::Delete => { - database_node._destroy = match DestroyNode::parse( - tokens, - idx, - depth + 1 - ) { + database_node._destroy = match DestroyNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), - Err(msg) => return Err(msg) - }}, + Err(msg) => return Err(msg), + } + } ImpliedAction::Show => { - database_node._show = match ShowNode::parse( - tokens, - idx, - depth + 1 - ) { + database_node._show = match ShowNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), - Err(msg) => return Err(msg) - }}, + Err(msg) => return Err(msg), + } + } ImpliedAction::Use => { - database_node._use = match UseNode::parse( - tokens, - idx, - depth + 1 - ) { + database_node._use = match UseNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), - Err(msg) => return Err(msg) - }}, - _ => return Err(format!( - "Got unexpected action requested for database -> '{:?}'", action) - ) - }; + Err(msg) => return Err(msg), + } + } + _ => { + return Err(format!( + "Got unexpected action requested for database -> '{:?}'", + action + )); + } + }; if tokens[*idx].token_type != TokenType::EoqToken { return Err(format!( "Unexpected token '{}', expected end-of-query token by this point.", - tokens[*idx].lexeme)) + tokens[*idx].lexeme + )); } return Ok(database_node); @@ -149,44 +131,27 @@ impl DatabaseNode { /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self, - ) -> (String, String) { - let pair: (String, (String, String)) = match ( - &self._create, - &self._destroy, - &self._show, - &self._use, - ) { - (Some(op), _, _, _) => ("CREATE DATABASE ".to_string(), op.transpile_color()), - (_, Some(op), _, _) => ("DROP DATABASE ".to_string(), op.transpile_color()), - (_, _, Some(op), _) => ("SHOW DATABASES".to_string(), op.transpile_color()), - (_, _, _, Some(op)) => ("USE DATABASE ".to_string(), op.transpile_color()), - _ => logger::error("No database operation provided"), - }; - + pub fn transpile_color(&self) -> (String, String) { + let pair: (String, (String, String)) = + match (&self._create, &self._destroy, &self._show, &self._use) { + (Some(op), _, _, _) => ("CREATE DATABASE ".to_string(), op.transpile_color()), + (_, Some(op), _, _) => ("DROP DATABASE ".to_string(), op.transpile_color()), + (_, _, Some(op), _) => ("SHOW DATABASES".to_string(), op.transpile_color()), + (_, _, _, Some(op)) => ("USE DATABASE ".to_string(), op.transpile_color()), + _ => logger::error("No database operation provided"), + }; + ( - colorize(&self._literal, AnsiColor::Yellow) + - if *&pair.1.0.len() != 0 { &" " } else { "" } + - &pair.1.0, - format!( - "{}{}", - colorize(&pair.0, AnsiColor::Yellow), - &pair.1.1 - ) + colorize(&self._literal, AnsiColor::Yellow) + + if *&pair.1.0.len() != 0 { &" " } else { "" } + + &pair.1.0, + format!("{}{}", colorize(&pair.0, AnsiColor::Yellow), &pair.1.1), ) } /// Outputs current AST node transpile to raw SQL. - pub fn transpile_raw( - &self - ) -> String { - match ( - &self._create, - &self._destroy, - &self._show, - &self._use, - ) { + pub fn transpile_raw(&self) -> String { + match (&self._create, &self._destroy, &self._show, &self._use) { (Some(op), _, _, _) => "CREATE DATABASE ".to_string() + &op.transpile_raw(), (_, Some(op), _, _) => "DROP DATABASE ".to_string() + &op.transpile_raw(), (_, _, Some(op), _) => "SHOW DATABASES".to_string() + &op.transpile_raw(), @@ -197,34 +162,31 @@ impl DatabaseNode { } impl CreateNode { - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16, - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; - + if tokens[*idx].token_type != TokenType::Identifier { - return Err(format!("Expected identifier, got '{:?}' instead!", tokens[*idx].token_type)); + return Err(format!( + "Expected identifier, got '{:?}' instead!", + tokens[*idx].token_type + )); } *idx += 1; validate_length(tokens, idx, true)?; - + return Ok(CreateNode { name: tokens[*idx - 1].literal.clone(), _literal: tokens[*idx - 1].lexeme.clone(), - _depth: depth - }) + _depth: depth, + }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( colorize(&self._literal.as_str(), AnsiColor::Blue), colorize(&self.name.as_str(), AnsiColor::Blue), @@ -232,9 +194,7 @@ impl CreateNode { } /// Outputs current AST node transpiled to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { self.name.clone() } } @@ -246,116 +206,96 @@ impl DestroyNode { idx: &mut usize, ) -> Result<(), String> { let next_token: TokenType = peek_one(tokens, &idx); - if - next_token != TokenType::And && - next_token != TokenType::Comma - { + if next_token != TokenType::And && next_token != TokenType::Comma { if tokens[*idx].token_type == TokenType::Identifier { dbs.push(tokens[*idx].literal.clone()); *idx += 1; return Ok(()); } - } else if - next_token == TokenType::And || - next_token == TokenType::Comma - { + } else if next_token == TokenType::And || next_token == TokenType::Comma { if tokens[*idx].token_type == TokenType::Identifier { dbs.push(tokens[*idx].literal.clone()); *idx += 2; - - DestroyNode::recurse_build( - tokens, - dbs, - idx - )?; + + DestroyNode::recurse_build(tokens, dbs, idx)?; return Ok(()); } } - + return Err("Something went wrong parsing database names, \ -make sure they're in a valid list notation.".to_string() - ); +make sure they're in a valid list notation." + .to_string()); } /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16, - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; - let start_idx: usize = *idx; + let start_idx: usize = *idx; let mut db_names: Vec = vec![]; DestroyNode::recurse_build(tokens, &mut db_names, idx)?; validate_length(tokens, idx, true)?; - + return Ok(DestroyNode { databases: db_names, - + _literal: { - tokens[start_idx..*idx].iter() + tokens[start_idx..*idx] + .iter() .map(|v| v.lexeme.as_str()) .collect::>() .join(" ") }, - _depth: depth - }) + _depth: depth, + }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( colorize(&self._literal.as_str(), AnsiColor::Blue), - colorize( &self.databases.join(", "), AnsiColor::Blue), + colorize(&self.databases.join(", "), AnsiColor::Blue), ) } /// Outputs current AST node transpile to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { self.databases.join(", ") } } impl UseNode { - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16, - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; - + if tokens[*idx].token_type != TokenType::Identifier { - return Err(format!("Expected identifier, got '{:?}' instead!", tokens[*idx].token_type)); + return Err(format!( + "Expected identifier, got '{:?}' instead!", + tokens[*idx].token_type + )); } *idx += 1; validate_length(tokens, idx, true)?; - + return Ok(UseNode { name: tokens[*idx - 1].literal.clone(), _literal: tokens[*idx - 1].lexeme.clone(), - _depth: depth - }) + _depth: depth, + }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( colorize(&self._literal.as_str(), AnsiColor::Blue), colorize(&self.name.as_str(), AnsiColor::Blue), @@ -363,42 +303,27 @@ impl UseNode { } /// Outputs current AST node transpiled to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { self.name.clone() } } impl ShowNode { - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16, - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; - - return Ok(ShowNode { - _depth: depth - }) + + return Ok(ShowNode { _depth: depth }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { - ( - "".to_string(), - "".to_string(), - ) + pub fn transpile_color(&self) -> (String, String) { + ("".to_string(), "".to_string()) } - + /// Outputs current AST node transpiled to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { "".to_string() } } @@ -408,7 +333,7 @@ impl fmt::Display for DatabaseNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(DatabaseNode){}{}{}{}", + "\n{}(DatabaseNode){}{}{}{}", get_tab(self._depth), self._create .as_ref() @@ -426,7 +351,6 @@ impl fmt::Display for DatabaseNode { .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), - ) } } @@ -435,7 +359,7 @@ impl fmt::Display for CreateNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(CreateNode) + "\n{}(CreateNode) {}name: {}", get_tab(self._depth), get_tab(self._depth + 1), @@ -448,7 +372,7 @@ impl fmt::Display for DestroyNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(DestroyNode) + "\n{}(DestroyNode) {}databases: {:?}", get_tab(self._depth), get_tab(self._depth + 1), @@ -461,7 +385,7 @@ impl fmt::Display for UseNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(UseNode) + "\n{}(UseNode) {}name: {}", get_tab(self._depth), get_tab(self._depth + 1), @@ -472,44 +396,32 @@ impl fmt::Display for UseNode { impl fmt::Display for ShowNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, -"\n{}(ShowNode)", - get_tab(self._depth), - ) + write!(f, "\n{}(ShowNode)", get_tab(self._depth),) } } - #[cfg(test)] mod tests { use super::*; #[test] fn unit_test_show_normal() { - let input: Vec = vec![ - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), - ]; + let input: Vec = vec![Token::new( + TokenType::EoqToken, + &"".to_string(), + &"!".to_string(), + )]; let mut idx: usize = 0; let depth: u16 = 0; - let expected: ShowNode = ShowNode { - _depth: depth - }; + let expected: ShowNode = ShowNode { _depth: depth }; - match ShowNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(val, expected), - Err(err) => assert!(false, "Output errored out -> {}", err) + match ShowNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), } } - + #[test] fn unit_test_use_normal() { let input: Vec = vec![ @@ -518,11 +430,7 @@ mod tests { &"test_db".to_string(), &"test_db".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; @@ -530,45 +438,35 @@ mod tests { let expected: UseNode = UseNode { name: "test_db".to_string(), _literal: "test_db".to_string(), - _depth: depth + _depth: depth, }; - match UseNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(val, expected), - Err(err) => assert!(true, "Output errored out -> {}", err) + match UseNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(true, "Output errored out -> {}", err), } } - + #[test] fn unit_test_use_error() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; - match UseNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(_) => assert!(true, "Output expected to error and did.") - } + match UseNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(_) => assert!(true, "Output expected to error and did."), + } } - + #[test] fn unit_test_create_normal() { let input: Vec = vec![ @@ -577,11 +475,7 @@ mod tests { &"test_db".to_string(), &"test_db".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; @@ -589,43 +483,33 @@ mod tests { let expected: CreateNode = CreateNode { name: "test_db".to_string(), _literal: "test_db".to_string(), - _depth: depth + _depth: depth, }; - match CreateNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(val, expected), - Err(err) => assert!(false, "Output errored out -> {}", err) + match CreateNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), } } - + #[test] fn unit_test_create_error() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; - match CreateNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(_) => assert!(true, "Output expected to error and did.") - } + match CreateNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(_) => assert!(true, "Output expected to error and did."), + } } #[test] @@ -636,11 +520,7 @@ mod tests { &"test_db".to_string(), &"test_db".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; @@ -648,18 +528,15 @@ mod tests { let expected: DestroyNode = DestroyNode { databases: vec!["test_db".to_string()], _literal: "test_db".to_string(), - _depth: depth + _depth: depth, }; - match DestroyNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(val, expected), - Err(err) => assert!(false, "Output errored out -> {}", err) + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), } } - + #[test] fn unit_test_destroy_normal_multiple() { let input: Vec = vec![ @@ -668,31 +545,19 @@ mod tests { &"test_db_1".to_string(), &"test_db_1".to_string(), ), - Token::new( - TokenType::Comma, - &",".to_string(), - &",".to_string(), - ), + Token::new(TokenType::Comma, &",".to_string(), &",".to_string()), Token::new( TokenType::Identifier, &"test_db_2".to_string(), &"test_db_2".to_string(), ), - Token::new( - TokenType::Comma, - &",".to_string(), - &",".to_string(), - ), + Token::new(TokenType::Comma, &",".to_string(), &",".to_string()), Token::new( TokenType::Identifier, &"test_db_3".to_string(), &"test_db_3".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; @@ -702,17 +567,14 @@ mod tests { "test_db_1".to_string(), "test_db_2".to_string(), "test_db_3".to_string(), - ], + ], _literal: "test_db_1 , test_db_2 , test_db_3".to_string(), - _depth: depth + _depth: depth, }; - match DestroyNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(val, expected), - Err(err) => assert!(false, "Output errored out -> {}", err) + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), } } @@ -724,16 +586,8 @@ mod tests { &"test_db_1".to_string(), &"test_db_1".to_string(), ), - Token::new( - TokenType::Comma, - &",".to_string(), - &",".to_string(), - ), - Token::new( - TokenType::Comma, - &",".to_string(), - &",".to_string(), - ), + Token::new(TokenType::Comma, &",".to_string(), &",".to_string()), + Token::new(TokenType::Comma, &",".to_string(), &",".to_string()), Token::new( TokenType::Identifier, &"test_db_2".to_string(), @@ -744,38 +598,32 @@ mod tests { let mut idx: usize = 0; let depth: u16 = 0; - match DestroyNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(_) => assert!(true, "Output expected to error and did.") - } + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(_) => assert!(true, "Output expected to error and did."), + } } #[test] fn unit_test_destroy_error() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &"!".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; - match DestroyNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(_) => assert!(true, "Output expected to error and did.") - } + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(_) => assert!(true, "Output expected to error and did."), + } } -} \ No newline at end of file +} diff --git a/src/language/parser/get.rs b/src/language/parser/get.rs index 444246c..3b51883 100644 --- a/src/language/parser/get.rs +++ b/src/language/parser/get.rs @@ -1,23 +1,15 @@ -use std::{fmt, usize}; use crate::{ language::{ parser::{ conditional::ConditionNode, - helpers::{ - get_tab, peek_one, validate_length - }, - postprocessor::{ - PostProcessorNode - }, - }, tokens::{ - Token, TokenType - } + helpers::{get_tab, peek_one, validate_length}, + postprocessor::PostProcessorNode, + }, + tokens::{Token, TokenType}, }, - utils::colors::{ - colorize, - AnsiColor - } + utils::colors::{AnsiColor, colorize}, }; +use std::{fmt, usize}; #[derive(Debug)] pub struct GetNode { @@ -26,7 +18,7 @@ pub struct GetNode { _filter: Option, _postprocessor: Option, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] @@ -34,8 +26,8 @@ pub struct TableNode { table_name: String, _literal: String, - _depth: u16 -} + _depth: u16, +} #[derive(Debug, PartialEq)] pub struct ColumnNode { @@ -43,7 +35,7 @@ pub struct ColumnNode { is_wildcard: bool, _literal: String, - _depth: u16 + _depth: u16, } #[derive(Debug, PartialEq)] @@ -51,86 +43,66 @@ pub struct FilterNode { condition: ConditionNode, _literal: String, - _depth: u16 + _depth: u16, } impl GetNode { - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16) -> Result{ - validate_length( - tokens, - idx, - true)?; - - let columns: ColumnNode = match ColumnNode::parse( - tokens, - idx, - depth + 1 - ) { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { + validate_length(tokens, idx, true)?; + + let columns: ColumnNode = match ColumnNode::parse(tokens, idx, depth + 1) { Ok(column) => column, - Err(err) => return Err(err) + Err(err) => return Err(err), }; - let table: TableNode = match TableNode::parse( - tokens, - idx, - depth + 1 - ) { + let table: TableNode = match TableNode::parse(tokens, idx, depth + 1) { Ok(table) => table, Err(err) => return Err(err), }; - let filter: Option = match FilterNode::parse( - tokens, - idx, - depth + 1 - ) { + let filter: Option = match FilterNode::parse(tokens, idx, depth + 1) { Ok(filter) => filter, Err(err) => return Err(err), }; - let postprocessor: Option = match PostProcessorNode::parse( - tokens, - idx, - depth + 1 - ) { - Ok(postprocessor) => postprocessor, - Err(msg) => return Err(msg) - }; + let postprocessor: Option = + match PostProcessorNode::parse(tokens, idx, depth + 1) { + Ok(postprocessor) => postprocessor, + Err(msg) => return Err(msg), + }; validate_length(tokens, idx, true)?; if tokens[*idx].token_type != TokenType::EoqToken { - return Err(format!("Unexpected token '{}', expected end-of-query token by this point.", tokens[*idx].lexeme)) + return Err(format!( + "Unexpected token '{}', expected end-of-query token by this point.", + tokens[*idx].lexeme + )); } Ok(GetNode { _table: table, _columns: columns, _filter: filter, - _postprocessor: postprocessor, + _postprocessor: postprocessor, - _depth: depth + _depth: depth, }) -} + } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self, - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { let columns: (String, String) = self._columns.transpile_color(); let table: (String, String) = self._table.transpile_color(); let filter: Option<(String, String)> = match &self._filter { Some(filter) => Some(filter.transpile_color()), - None => None + None => None, }; let postprocessor: Option<(String, String)> = match &self._postprocessor { Some(postprocessor) => Some(postprocessor.transpile_color()), - None => None + None => None, }; ( @@ -140,36 +112,34 @@ impl GetNode { filter.as_ref().map(|f| f.0.clone()), postprocessor.as_ref().map(|f| f.0.clone()), ] - .into_iter() - .flatten() - .collect::>() - .join(" "), + .into_iter() + .flatten() + .collect::>() + .join(" "), [ Some(columns.1), Some(table.1), filter.as_ref().map(|f| f.1.clone()), postprocessor.as_ref().map(|f| f.1.clone()), ] - .into_iter() - .flatten() - .collect::>() - .join(" "), + .into_iter() + .flatten() + .collect::>() + .join(" "), ) } /// Outputs current AST node transpiled to raw SQL - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { let columns: String = self._columns.transpile_raw(); let table: String = self._table.transpile_raw(); let filter: Option = match &self._filter { Some(filter) => Some(filter.transpile_raw()), - None => None + None => None, }; let postprocessor: Option = match &self._postprocessor { Some(postprocessor) => Some(postprocessor.transpile_raw()), - None => None + None => None, }; [ @@ -178,10 +148,10 @@ impl GetNode { filter.as_ref().map(|f| f.clone()), postprocessor.as_ref().map(|f| f.clone()), ] - .into_iter() - .flatten() - .collect::>() - .join(" ") + .into_iter() + .flatten() + .collect::>() + .join(" ") } } @@ -189,15 +159,12 @@ impl TableNode { /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16 - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { let start_idx: usize = *idx; - if tokens[*idx].token_type == TokenType::From && - peek_one(tokens, idx) == TokenType::Identifier { + if tokens[*idx].token_type == TokenType::From + && peek_one(tokens, idx) == TokenType::Identifier + { *idx += 1; } else { return Err(format!( @@ -208,87 +175,90 @@ impl TableNode { *idx += 1; - return Ok( - TableNode { - table_name: tokens[*idx - 1].literal.clone(), - - _literal: { - tokens[start_idx..*idx].iter() - .map(|v| v.lexeme.as_str()) - .collect::>() - .join(" ") - }, + return Ok(TableNode { + table_name: tokens[*idx - 1].literal.clone(), + _literal: { + tokens[start_idx..*idx] + .iter() + .map(|v| v.lexeme.as_str()) + .collect::>() + .join(" ") + }, - _depth: depth + _depth: depth, }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( colorize(&self._literal, AnsiColor::Blue), - colorize(&format!("FROM {}", self.table_name), AnsiColor::Blue) + colorize(&format!("FROM {}", self.table_name), AnsiColor::Blue), ) } /// Outputs current AST node transpiled to raw SQL - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { format!("FROM {}", self.table_name) } } impl ColumnNode { - pub fn recurse_build( + fn recurse_build( tokens: &Vec, cols: &mut Vec, idx: &mut usize, ) -> Result<(), String> { let next_token: TokenType = peek_one(tokens, &idx); - if next_token != TokenType::And && - next_token != TokenType::Comma { + if next_token != TokenType::And && next_token != TokenType::Comma { if tokens[*idx].token_type == TokenType::Identifier { cols.push(tokens[*idx].literal.clone()); *idx += 1; return Ok(()); } - } else if next_token == TokenType::And || - next_token == TokenType::Comma { + } else if next_token == TokenType::And || next_token == TokenType::Comma { if tokens[*idx].token_type == TokenType::Identifier { cols.push(tokens[*idx].literal.clone()); *idx += 2; - - ColumnNode::recurse_build( - tokens, - cols, - idx - )?; + + ColumnNode::recurse_build(tokens, cols, idx)?; return Ok(()); } } - + return Err("Something went wrong parsing column names, \ -make sure they're in a valid list notation.".to_string() - ); +make sure they're in a valid list notation." + .to_string()); + } + + /// Reconstructs original literal from list of tokens and + /// provided bounds. + fn reconstruct_literal(tokens: &Vec, start_idx: usize, end_idx: usize) -> String { + let mut literal = String::new(); + + literal.push_str(&format!("{} ", &tokens[start_idx].lexeme).to_string()); + + for v in &tokens[start_idx + 1..end_idx] { + match v.token_type { + TokenType::Comma => literal.push_str(", "), + TokenType::And => literal.push_str(" and "), + _ => literal.push_str(&v.lexeme), + }; + } + + literal } /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16 - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { // We subtract 1 from this because Get keyword has been processed already - let start_idx: usize = *idx - 1; + let start_idx: usize = *idx - 1; if tokens[*idx].token_type == TokenType::WildcardKeyword { *idx += 1; @@ -298,57 +268,47 @@ make sure they're in a valid list notation.".to_string() column_names: vec![], _literal: { - tokens[start_idx..*idx].iter() + tokens[start_idx..*idx] + .iter() .map(|v| v.lexeme.as_str()) .collect::>() .join(" ") - }, - _depth: depth + }, + _depth: depth, }); } let mut column_names: Vec = vec![]; - + ColumnNode::recurse_build(tokens, &mut column_names, idx)?; - return Ok( - ColumnNode { - is_wildcard: false, - column_names: column_names, + return Ok(ColumnNode { + is_wildcard: false, + column_names: column_names, - _literal: { - tokens[start_idx..*idx].iter() - .map(|v| v.lexeme.as_str()) - .collect::>() - .join(" ") - }, - _depth: depth + _literal: ColumnNode::reconstruct_literal(tokens, start_idx, *idx), + _depth: depth, }); } /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { let columns: String = if self.is_wildcard { "*".to_string() } else { self.column_names.join(", ") }; - ( colorize(&self._literal, AnsiColor::Yellow), - colorize(&format!("SELECT {}", columns), AnsiColor::Yellow) + colorize(&format!("SELECT {}", columns), AnsiColor::Yellow), ) } /// Outputs current AST node transpiled to raw SQL - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { let columns: String = if self.is_wildcard { "*".to_string() } else { @@ -366,35 +326,24 @@ impl FilterNode { pub fn parse( tokens: &Vec, idx: &mut usize, - depth: u16) -> Result, String> { - + depth: u16, + ) -> Result, String> { if tokens[*idx].token_type == TokenType::FilterKeyword { let start_idx: usize = *idx; *idx += 1; - let condition_node: ConditionNode = match ConditionNode::parse( - tokens, - idx, - depth + 1) { - Ok(condition) => { - condition - }, - Err(err) => { - return Err(err); - } + let condition_node: ConditionNode = match ConditionNode::parse(tokens, idx, depth + 1) { + Ok(condition) => condition, + Err(err) => { + return Err(err); + } }; - return Ok( - Some(FilterNode { - condition: condition_node, - - _literal: { - tokens[start_idx..*idx].iter() - .map(|v| v.lexeme.as_str()) - .collect::>() - .join(" ") - }, - _depth: depth + return Ok(Some(FilterNode { + condition: condition_node, + + _literal: tokens[start_idx].lexeme.clone(), + _depth: depth, })); } @@ -404,19 +353,21 @@ impl FilterNode { /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( - colorize(&self._literal, AnsiColor::Cyan), - colorize(&format!("WHERE {}", self.condition.transpile_color()), AnsiColor::Cyan) + colorize( + &format!("{} {}", self._literal, self.condition.transpile_raw()), + AnsiColor::Cyan, + ), + colorize( + &format!("WHERE {}", self.condition.transpile_color()), + AnsiColor::Cyan, + ), ) } - + /// Outputs current AST node transpiled to raw SQL - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { format!("WHERE {}", self.condition.transpile_raw()) } } @@ -426,26 +377,30 @@ impl fmt::Display for GetNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(GetNode){}{}{}{}", + "\n{}(GetNode){}{}{}{}", get_tab(self._depth), self._columns, self._table, self._filter .as_ref() .map(|v| v as &dyn fmt::Display) - .unwrap_or(&format!(" + .unwrap_or(&format!( + " {}(FilterNode) {}N/A", - get_tab(self._depth + 1), - get_tab(self._depth + 2))), + get_tab(self._depth + 1), + get_tab(self._depth + 2) + )), self._postprocessor .as_ref() .map(|v| v as &dyn fmt::Display) - .unwrap_or(&format!(" + .unwrap_or(&format!( + " {}(PostProcessorNode) {}N/A", - get_tab(self._depth + 1), - get_tab(self._depth + 2))) + get_tab(self._depth + 1), + get_tab(self._depth + 2) + )) ) } } @@ -454,7 +409,7 @@ impl fmt::Display for FilterNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(FilterNode){}", + "\n{}(FilterNode){}", get_tab(self._depth), self.condition ) @@ -465,7 +420,7 @@ impl fmt::Display for TableNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(TableNode) + "\n{}(TableNode) {}table_name: {:?}", get_tab(self._depth), get_tab(self._depth + 1), @@ -478,7 +433,7 @@ impl fmt::Display for ColumnNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(ColumnNode) + "\n{}(ColumnNode) {}is_wildcard: {:?} {}column_names: {:?}", get_tab(self._depth), @@ -498,53 +453,34 @@ mod tests { #[test] fn unit_test_column_parsing_error() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::Identifier, - &"id".to_string(), - &"id".to_string(), - ), - Token::new( - TokenType::Comma, - &"".to_string(), - &",".to_string(), - ), - Token::new( - TokenType::And, - &"".to_string(), - &"and".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::Identifier, &"id".to_string(), &"id".to_string()), + Token::new(TokenType::Comma, &"".to_string(), &",".to_string()), + Token::new(TokenType::And, &"".to_string(), &"and".to_string()), Token::new( TokenType::Identifier, &"time".to_string(), - &"time".to_string() - ) + &"time".to_string(), + ), ]; let mut idx: usize = 1; let depth: u16 = 0; - - match ColumnNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(err) => assert!(true, "Output errored out -> {}", err) + + match ColumnNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(err) => assert!(true, "Output errored out -> {}", err), } } - + #[test] fn unit_test_column_parsing_normal_wildcard() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), Token::new( TokenType::WildcardKeyword, &"".to_string(), @@ -557,144 +493,100 @@ mod tests { is_wildcard: true, _literal: "get all".to_string(), - _depth: 0 + _depth: 0, }; let mut idx: usize = 1; let depth: u16 = 0; - - match ColumnNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(expected, val), - Err(err) => assert!(false, "Output errored out -> {}", err) - } + + match ColumnNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(expected, val), + Err(err) => assert!(false, "Output errored out -> {}", err), + } } #[test] fn unit_test_column_parsing_normal_single() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::Identifier, - &"id".to_string(), - &"id".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::Identifier, &"id".to_string(), &"id".to_string()), ]; let expected: ColumnNode = ColumnNode { - column_names: vec![ - "id".to_string(), - ], + column_names: vec!["id".to_string()], is_wildcard: false, _literal: "get id".to_string(), - _depth: 0 + _depth: 0, }; let mut idx: usize = 1; let depth: u16 = 0; - - match ColumnNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(expected, val), - Err(err) => assert!(false, "Output errored out -> {}", err) - } + + match ColumnNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(expected, val), + Err(err) => assert!(false, "Output errored out -> {}", err), + } } #[test] fn unit_test_column_parsing_normal_multiple() { let input: Vec = vec![ - Token::new( - TokenType::Get, - &"get".to_string(), - &"get".to_string(), - ), - Token::new( - TokenType::Identifier, - &"id".to_string(), - &"id".to_string(), - ), - Token::new( - TokenType::Comma, - &"".to_string(), - &",".to_string(), - ), + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::Identifier, &"id".to_string(), &"id".to_string()), + Token::new(TokenType::Comma, &"".to_string(), &",".to_string()), Token::new( TokenType::Identifier, &"cost".to_string(), &"cost".to_string(), ), - Token::new( - TokenType::And, - &"".to_string(), - &"and".to_string(), - ), + Token::new(TokenType::And, &"".to_string(), &"and".to_string()), Token::new( TokenType::Identifier, &"time".to_string(), - &"time".to_string() - ) + &"time".to_string(), + ), ]; let expected: ColumnNode = ColumnNode { - column_names: vec![ - "id".to_string(), - "cost".to_string(), - "time".to_string() - ], + column_names: vec!["id".to_string(), "cost".to_string(), "time".to_string()], is_wildcard: false, - _literal: "get id , cost and time".to_string(), - _depth: 0 + _literal: "get id, cost and time".to_string(), + _depth: 0, }; let mut idx: usize = 1; let depth: u16 = 0; - - match ColumnNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(expected, val), - Err(err) => assert!(false, "Output errored out -> {}", err) - } + + match ColumnNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(expected, val), + Err(err) => assert!(false, "Output errored out -> {}", err), + } } #[test] fn unit_test_table_error() { - let input: Vec = vec![ - Token::new( - TokenType::From, - &"".to_string(), - &"from".to_string(), - ), - ]; + let input: Vec = vec![Token::new( + TokenType::From, + &"".to_string(), + &"from".to_string(), + )]; let mut idx: usize = 0; let depth: u16 = 0; - - match TableNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert!(false, "Output was expected to error but returned -> {}", val), - Err(err) => assert!(true, "Output errored out -> {}", err) + + match TableNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(err) => assert!(true, "Output errored out -> {}", err), } } #[test] fn unit_test_table_normal() { let input: Vec = vec![ - Token::new( - TokenType::From, - &"".to_string(), - &"from".to_string(), - ), + Token::new(TokenType::From, &"".to_string(), &"from".to_string()), Token::new( TokenType::Identifier, &"table_name".to_string(), @@ -704,19 +596,16 @@ mod tests { let expected: TableNode = TableNode { table_name: "table_name".to_string(), - + _literal: "from table_name".to_string(), - _depth: 0 + _depth: 0, }; let mut idx: usize = 0; let depth: u16 = 0; - - match TableNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => assert_eq!(expected, val), - Err(err) => assert!(false, "Output errored out -> {}", err) - } + + match TableNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(expected, val), + Err(err) => assert!(false, "Output errored out -> {}", err), + } } -} \ No newline at end of file +} diff --git a/src/language/parser/helpers.rs b/src/language/parser/helpers.rs index 5a0efa2..ce7121f 100644 --- a/src/language/parser/helpers.rs +++ b/src/language/parser/helpers.rs @@ -1,57 +1,48 @@ -use crate::{ - language::{ - tokens::{ - Token, TokenType - } - } -}; +use crate::language::tokens::{Token, TokenType}; const TAB_SIZE: u16 = 2; -pub fn validate_length( - tokens: &Vec, - idx: &usize, - required: bool -) -> Result<(), String>{ +pub fn validate_length(tokens: &Vec, idx: &usize, required: bool) -> Result<(), String> { if *idx >= tokens.len() && required { - return Err( - format!("Query valid until after \"{}\"! Possible unfinished query?", - tokens[..*idx].into_iter().map( - |x| x.lexeme.as_str() - ).collect::>().join(" ")).to_string()); + return Err(format!( + "Query valid until after \"{}\"! Possible unfinished query?", + tokens[..*idx] + .into_iter() + .map(|x| x.lexeme.as_str()) + .collect::>() + .join(" ") + ) + .to_string()); } Ok(()) } -pub fn get_tab( - depth: u16 -) -> String { +pub fn get_tab(depth: u16) -> String { " ".repeat(TAB_SIZE as usize * depth as usize) } -pub fn valid_until_warning( - tokens: &Vec, - idx: &usize -) -> String { +pub fn valid_until_warning(tokens: &Vec, idx: &usize) -> String { if *idx >= tokens.len() { return "N/A".to_string(); } - format!("Query valid until after \"{}\"!", - tokens[..*idx].into_iter().map( - |x| x.lexeme.as_str() - ).collect::>().join(" ")).to_string() + format!( + "Query valid until after \"{}\"!", + tokens[..*idx] + .into_iter() + .map(|x| x.lexeme.as_str()) + .collect::>() + .join(" ") + ) + .to_string() } // Look a token ahead -pub fn peek_one( - tokens: &Vec, - idx: &usize -) -> TokenType { +pub fn peek_one(tokens: &Vec, idx: &usize) -> TokenType { if *idx + 1 >= tokens.len() { TokenType::NullToken } else { tokens[*idx + 1].token_type.clone() } -} \ No newline at end of file +} diff --git a/src/language/parser/mod.rs b/src/language/parser/mod.rs index 002a2d5..0271cd0 100644 --- a/src/language/parser/mod.rs +++ b/src/language/parser/mod.rs @@ -1,6 +1,6 @@ +pub mod conditional; +pub mod database; pub mod get; pub mod helpers; pub mod parser; -pub mod conditional; pub mod postprocessor; -pub mod database; \ No newline at end of file diff --git a/src/language/parser/parser.rs b/src/language/parser/parser.rs index ff5a1bc..72a09c4 100644 --- a/src/language/parser/parser.rs +++ b/src/language/parser/parser.rs @@ -1,23 +1,15 @@ -use std::{fmt}; use crate::{ - utils::{ - logger - }, language::{ + parser::database::DatabaseNode, parser::{ - helpers::{ - validate_length, get_tab - }, - get::GetNode - }, - tokens::{ - Token, TokenType + get::GetNode, + helpers::{get_tab, validate_length}, }, - parser::database::{ - DatabaseNode - } - } + tokens::{Token, TokenType}, + }, + utils::logger, }; +use std::fmt; #[derive(Debug, PartialEq)] pub enum ImpliedAction { @@ -25,14 +17,14 @@ pub enum ImpliedAction { Delete, Create, Show, - _Rename + _Rename, } #[derive(Debug)] pub struct Query { _get: Option, _database: Option, - _depth: u16 + _depth: u16, } impl ImpliedAction { @@ -42,7 +34,10 @@ impl ImpliedAction { TokenType::DeleteKeyword => Ok(ImpliedAction::Delete), TokenType::ShowKeyword => Ok(ImpliedAction::Show), TokenType::UseKeyword => Ok(ImpliedAction::Use), - _ => Err(format!("Invalid action token type encountered -> got {:?}", value)) + _ => Err(format!( + "Invalid action token type encountered -> got {:?}", + value + )), } } } @@ -51,33 +46,26 @@ impl Query { /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16 - ) -> Result { - validate_length( - &tokens, - &idx, - true)?; - + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { + validate_length(&tokens, &idx, true)?; + if tokens[*idx].token_type == TokenType::Get { *idx += 1; - let get_node: GetNode = GetNode::parse( - &tokens, idx, depth + 1 - )?; + let get_node: GetNode = GetNode::parse(&tokens, idx, depth + 1)?; return Ok(Query { _get: Some(get_node), _database: None, - _depth: depth + _depth: depth, }); } else if vec![ TokenType::CreateKeyword, TokenType::DeleteKeyword, TokenType::UseKeyword, - TokenType::ShowKeyword - ].contains(&tokens[*idx].token_type) { + TokenType::ShowKeyword, + ] + .contains(&tokens[*idx].token_type) + { validate_length(tokens, &(*idx + 1), true)?; *idx += 1; @@ -86,24 +74,22 @@ impl Query { *idx += 1; let database_node: DatabaseNode = DatabaseNode::parse( - &tokens, - idx, + &tokens, + idx, depth + 1, - ImpliedAction::try_from(tokens[*idx - 2].token_type)? + ImpliedAction::try_from(tokens[*idx - 2].token_type)?, )?; return Ok(Query { _get: None, _database: Some(database_node), - _depth: depth + _depth: depth, }); } else { - return Err( - format!( - "Query recieved an action keyword, but received an invalid target keyword `{:?}`. Valid targets are: `Database`", - tokens[*idx].token_type - ) - ) + return Err(format!( + "Query recieved an action keyword, but received an invalid target keyword `{:?}`. Valid targets are: `Database`", + tokens[*idx].token_type + )); } } @@ -113,9 +99,7 @@ impl Query { /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { if let Some(get) = &self._get { return get.transpile_color(); } else if let Some(database) = &self._database { @@ -126,9 +110,7 @@ impl Query { } /// Ouputs current AST node tranpile to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { if let Some(get) = &self._get { return get.transpile_raw(); } else if let Some(database) = &self._database { @@ -136,14 +118,14 @@ impl Query { } else { logger::error("A fatal error occurred while transpiling your query!"); }; - } + } } impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"{}(Query){}{}", + "{}(Query){}{}", get_tab(self._depth), self._get .as_ref() @@ -152,7 +134,8 @@ impl fmt::Display for Query { self._database .as_ref() .map(|v| v as &dyn fmt::Display) - .unwrap_or(&"")) + .unwrap_or(&"") + ) } } @@ -165,14 +148,14 @@ pub fn parse(tokens: &Vec) -> Result { Query::parse(tokens, &mut idx, 0) } -/* Template for Nodes +/* Template for Nodes impl TemplateNode { pub fn parse( tokens: &Vec, idx: &mut usize, depth: u16) -> Result { - + return Err("ERROR PLACEHOLDER".to_string()); } } -*/ \ No newline at end of file +*/ diff --git a/src/language/parser/postprocessor.rs b/src/language/parser/postprocessor.rs index 39a7189..e385218 100644 --- a/src/language/parser/postprocessor.rs +++ b/src/language/parser/postprocessor.rs @@ -1,71 +1,61 @@ -use std::{fmt, usize}; use crate::{ language::{ - parser::helpers::{ - get_tab, peek_one, validate_length - }, - tokens::{ - Token, TokenType - } + parser::helpers::{get_tab, peek_one, validate_length}, + tokens::{Token, TokenType}, }, - utils::{ - colors::{ - colorize, - AnsiColor - } - } + utils::colors::{AnsiColor, colorize}, }; +use std::{fmt, usize}; #[derive(PartialEq, Debug)] pub struct LimitNode { limit: i32, _literal: String, - _depth: u16 + _depth: u16, } #[derive(PartialEq, Debug)] pub struct PostProcessorNode { pub limit: Option, - _depth: u16 + _depth: u16, } impl LimitNode { /// Takes current node type and given the current location in the /// query defined by the borrowed index, makes an attempt to parse /// this node and associated subnodes for the Abstract Syntax Tree. - pub fn parse( - tokens: &Vec, - idx: &mut usize, - depth: u16 - ) -> Result { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; - + let start_idx: usize = *idx - 1; if tokens[*idx].token_type != TokenType::NumberLiteral { - return Err( - format!( - "Limit post-processor expects a number literal, got -> {:?}", - tokens[*idx].token_type) - ); + return Err(format!( + "Limit post-processor expects a number literal, got -> {:?}", + tokens[*idx].token_type + )); } return Ok(LimitNode { limit: match tokens[*idx].literal.parse::() { Ok(state) => state, - Err(_) => return Err( - format!("Limit post-processor expects 32-bit integer, got -> {}", tokens[*idx].lexeme) - ) + Err(_) => { + return Err(format!( + "Limit post-processor expects 32-bit integer, got -> {}", + tokens[*idx].lexeme + )); + } }, _literal: { - tokens[start_idx..*idx + 1].iter() + tokens[start_idx..*idx + 1] + .iter() .map(|v| v.lexeme.as_str()) .collect::>() .join(" ") - }, + }, _depth: depth, }); } @@ -73,19 +63,15 @@ impl LimitNode { /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { ( colorize(&self._literal, AnsiColor::Magenta), - colorize(&format!("LIMIT {}", self.limit), AnsiColor::Magenta) + colorize(&format!("LIMIT {}", self.limit), AnsiColor::Magenta), ) } /// Outputs current AST node transpiled to raw SQL. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { format!("LIMIT {}", self.limit) } } @@ -102,19 +88,21 @@ impl PostProcessorNode { match tokens[*idx].token_type { TokenType::And => { *idx += 1; - }, + } TokenType::LimitKeyword => { *idx += 1; final_node.limit = match LimitNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), - Err(msg) => return Err(msg) + Err(msg) => return Err(msg), }; - }, - _ => return Err( - format!( - "Unexpected token, expected post-processor entrance keyword or list continuation, got -> \"{}\"", - tokens[*idx].lexeme)) + } + _ => { + return Err(format!( + "Unexpected token, expected post-processor entrance keyword or list continuation, got -> \"{}\"", + tokens[*idx].lexeme + )); + } }; Ok(()) @@ -132,19 +120,10 @@ impl PostProcessorNode { if next_token == TokenType::EoqToken { *idx += 1; return Ok(()); - } else { - PostProcessorNode::handle_postprocessor( - tokens, - final_node, - depth, - idx)?; - - PostProcessorNode::recurse_build( - tokens, - final_node, - depth, - idx - )?; + } else { + PostProcessorNode::handle_postprocessor(tokens, final_node, depth, idx)?; + + PostProcessorNode::recurse_build(tokens, final_node, depth, idx)?; return Ok(()); } @@ -156,7 +135,7 @@ impl PostProcessorNode { pub fn parse( tokens: &Vec, idx: &mut usize, - depth: u16 + depth: u16, ) -> Result, String> { validate_length(tokens, idx, true)?; @@ -168,32 +147,23 @@ impl PostProcessorNode { let mut final_node: PostProcessorNode = PostProcessorNode { limit: None, - _depth: depth + _depth: depth, }; - PostProcessorNode::recurse_build( - tokens, - &mut final_node, - depth, - idx)?; + PostProcessorNode::recurse_build(tokens, &mut final_node, depth, idx)?; return Ok(Some(final_node)); } - + /// Outputs current AST node transpiled with color /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. - pub fn transpile_color( - &self - ) -> (String, String) { + pub fn transpile_color(&self) -> (String, String) { let mut final_lexeme: Vec = vec![]; let mut final_transpiled: Vec = vec![]; if self.limit.is_some() { - let limit_transpiled: (String, String) = self.limit - .as_ref() - .unwrap() - .transpile_color(); + let limit_transpiled: (String, String) = self.limit.as_ref().unwrap().transpile_color(); final_lexeme.push(limit_transpiled.0); final_transpiled.push(limit_transpiled.1); @@ -203,16 +173,11 @@ impl PostProcessorNode { } /// outputs current ast node transpiled to raw sql. - pub fn transpile_raw( - &self - ) -> String { + pub fn transpile_raw(&self) -> String { let mut final_transpiled: Vec = vec![]; if self.limit.is_some() { - let limit_transpiled: String = self.limit - .as_ref() - .unwrap() - .transpile_raw(); + let limit_transpiled: String = self.limit.as_ref().unwrap().transpile_raw(); final_transpiled.push(limit_transpiled); } @@ -225,13 +190,13 @@ impl fmt::Display for PostProcessorNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(PostProcessorNode){}", + "\n{}(PostProcessorNode){}", get_tab(self._depth), self.limit .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&"") - ) + ) } } @@ -239,7 +204,7 @@ impl fmt::Display for LimitNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, -"\n{}(LimitNode) + "\n{}(LimitNode) {}limit: {:?}", get_tab(self._depth), get_tab(self._depth + 1), @@ -255,23 +220,18 @@ mod tests { #[test] fn unit_test_postprocessor_error() { - let input: Vec = vec![ - Token::new( - TokenType::PostProcessorEntrance, - &"".to_string(), - &"then".to_string(), - ), - ]; + let input: Vec = vec![Token::new( + TokenType::PostProcessorEntrance, + &"".to_string(), + &"then".to_string(), + )]; let mut idx: usize = 0; let depth: u16 = 0; - match PostProcessorNode::parse( - &input, - &mut idx, - depth) { - Ok(_) => assert!(false, "Output was expected to error!"), - Err(err) => assert!(true, "Output errored out -> {}", err) + match PostProcessorNode::parse(&input, &mut idx, depth) { + Ok(_) => assert!(false, "Output was expected to error!"), + Err(err) => assert!(true, "Output errored out -> {}", err), } } @@ -293,22 +253,15 @@ mod tests { &"5.5".to_string(), &"5.5".to_string(), ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &".".to_string() - ) + Token::new(TokenType::EoqToken, &"".to_string(), &".".to_string()), ]; let mut idx: usize = 0; let depth: u16 = 0; - - match PostProcessorNode::parse( - &input, - &mut idx, - depth) { - Ok(_) => assert!(false, "Output was expected to error!"), - Err(err) => assert!(true, "Output errored out -> {}", err) + + match PostProcessorNode::parse(&input, &mut idx, depth) { + Ok(_) => assert!(false, "Output was expected to error!"), + Err(err) => assert!(true, "Output errored out -> {}", err), } } @@ -325,16 +278,8 @@ mod tests { &"".to_string(), &"limit".to_string(), ), - Token::new( - TokenType::NumberLiteral, - &"5".to_string(), - &"5".to_string(), - ), - Token::new( - TokenType::EoqToken, - &"".to_string(), - &".".to_string() - ) + Token::new(TokenType::NumberLiteral, &"5".to_string(), &"5".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &".".to_string()), ]; let expected: PostProcessorNode = PostProcessorNode { @@ -344,21 +289,17 @@ mod tests { _depth: 1, _literal: "limit 5".to_string(), }), - _depth: 0 + _depth: 0, }; let mut idx: usize = 0; let depth: u16 = 0; - - match PostProcessorNode::parse( - &input, - &mut idx, - depth) { - Ok(val) => - match val { - Some(node) => assert_eq!(node, expected), - None => assert!(false, "Output returned nothing but something was expected!") - } - Err(err) => assert!(false, "Output errored out -> {}", err) - } + + match PostProcessorNode::parse(&input, &mut idx, depth) { + Ok(val) => match val { + Some(node) => assert_eq!(node, expected), + None => assert!(false, "Output returned nothing but something was expected!"), + }, + Err(err) => assert!(false, "Output errored out -> {}", err), + } } -} \ No newline at end of file +} diff --git a/src/language/tokens.rs b/src/language/tokens.rs index 95c5069..0c03647 100644 --- a/src/language/tokens.rs +++ b/src/language/tokens.rs @@ -1,26 +1,50 @@ -use std::{collections::HashMap, fmt}; use lazy_static::lazy_static; +use std::{collections::HashMap, fmt}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum TokenType { // Single Char Tokens - OpenParen, CloseParen, EoqToken, + OpenParen, + CloseParen, + EoqToken, Comma, - + // One or Two Char Tokens - Gte, Lte, Equal, Lt, Gt, + Gte, + Lte, + Equal, + Lt, + Gt, // Literals - Identifier, StringLiteral, NumberLiteral, + Identifier, + StringLiteral, + NumberLiteral, // Keywords - DeleteKeyword, CreateKeyword, SortHelper, SortType, - WildcardKeyword, FilterKeyword, PostProcessorEntrance, - Database, Get, From, And, Or, Order, Sort, Not, LimitKeyword, - UseKeyword, ShowKeyword, + DeleteKeyword, + CreateKeyword, + SortHelper, + SortType, + WildcardKeyword, + FilterKeyword, + PostProcessorEntrance, + Database, + Get, + From, + And, + Or, + Order, + Sort, + Not, + LimitKeyword, + UseKeyword, + ShowKeyword, // Defaults - UnknownToken, WhitespaceToken, NullToken + UnknownToken, + WhitespaceToken, + NullToken, } #[derive(Debug, PartialEq, Clone)] @@ -31,15 +55,11 @@ pub struct Token { } impl Token { - pub fn new( - token_type: TokenType, - literal: &String, - lexeme: &String - ) -> Token { + pub fn new(token_type: TokenType, literal: &String, lexeme: &String) -> Token { Token { token_type: token_type, literal: literal.to_owned(), - lexeme: lexeme.to_owned() + lexeme: lexeme.to_owned(), } } } @@ -49,21 +69,15 @@ impl fmt::Display for Token { write!( f, "Token {{ Type: {:#?}, Literal: {}, Lexeme: {} }}", - self.token_type, self.literal, self.lexeme) + self.token_type, self.literal, self.lexeme + ) } } - // These are for finding unique cases of tokens -pub const SINGLE_START_TOKENS: &[char] = &[ - '(', ')', '!', '.', ';', ',' -]; - - -pub const SINGLE_DOUBLE_START_TOKENS: &[char] = &[ - '<', '>', '=' -]; +pub const SINGLE_START_TOKENS: &[char] = &['(', ')', '!', '.', ';', ',']; +pub const SINGLE_DOUBLE_START_TOKENS: &[char] = &['<', '>', '=']; lazy_static! { pub static ref IDENTIFER_STOPS: Vec = { @@ -141,4 +155,4 @@ lazy_static! { ("to", TokenType::NullToken) ]); }; -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 91b9b89..2c5a588 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ // This is the main interface to the interworkings of the EAQL language pub mod language; -pub mod validator; +pub mod transpiler; pub mod utils; -pub mod transpiler; \ No newline at end of file +pub mod validator; diff --git a/src/transpiler/mod.rs b/src/transpiler/mod.rs index 531a51c..33701f5 100644 --- a/src/transpiler/mod.rs +++ b/src/transpiler/mod.rs @@ -1,2 +1,2 @@ pub mod transpiler; -pub use transpiler::*; \ No newline at end of file +pub use transpiler::*; diff --git a/src/transpiler/transpiler.rs b/src/transpiler/transpiler.rs index 1fbeb62..6889540 100644 --- a/src/transpiler/transpiler.rs +++ b/src/transpiler/transpiler.rs @@ -1,14 +1,10 @@ use crate::{ language::parser::parser::Query, utils::{ - colors::{ - colorize, - AnsiColor - }, - io, - logger, - query::process_query - } + colors::{AnsiColor, colorize}, + io, logger, + query::process_query, + }, }; /// Starts a Transpiling loop that accepts queries from STDIN @@ -22,23 +18,27 @@ pub fn repl_loop() { Some(state) => state, None => { logger::warning("Invalid query, see above warnings for issues!"); - continue + continue; } }; let transpiled: (String, String) = parsed.transpile_color(); - + println!( "‣ {} {};", - colorize("Reduced Query:", AnsiColor::BrightBlack), transpiled.0); + colorize("Reduced Query:", AnsiColor::BrightBlack), + transpiled.0 + ); println!( "‣ {} {};", - colorize("SQL Query:", AnsiColor::BrightBlack), transpiled.1); - }; + colorize("SQL Query:", AnsiColor::BrightBlack), + transpiled.1 + ); + } } /// Transpile Input Query (String) to SQL -/// +/// /// # Example /// ``` /// use eaql::transpiler::engine; @@ -54,4 +54,4 @@ pub fn engine(query: &str) -> Result { }; return Ok(format!("{};", parsed.transpile_raw())); -} \ No newline at end of file +} diff --git a/src/utils/io.rs b/src/utils/io.rs index c473803..d03233d 100644 --- a/src/utils/io.rs +++ b/src/utils/io.rs @@ -1,7 +1,5 @@ +use crate::utils::logger; use std::io::{self, Write}; -use crate::{ - utils::logger -}; pub fn query_stdin(tag: &str) -> String { // Get input @@ -9,7 +7,7 @@ pub fn query_stdin(tag: &str) -> String { print!("({}) >>> ", tag); io::stdout().flush().unwrap(); io::stdin().read_line(&mut line).unwrap(); - line = line.trim().to_string(); + line = line.trim().to_string(); logger::debug(&format!("Received Query String -> \"{}\"", line)); line } diff --git a/src/utils/logger.rs b/src/utils/logger.rs index b4b913a..9e7868e 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,9 +1,6 @@ // EAQL Logger - Will need to clean this up later +use crate::utils::colors::{AnsiColor, colorize}; use chrono::Local; -use crate::utils::colors::{ - AnsiColor, - colorize -}; // TODO: Move these to config? pub const TEST_MODE: bool = true; @@ -27,7 +24,6 @@ fn send_msg(log_level: &str, msg: &str) -> String { format!("[{timestamp}][{prefix}] {msg}") } - fn get_timestamp() -> String { return Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); } @@ -50,6 +46,6 @@ pub fn warning(msg: &str) -> () { } } -pub fn error(msg: &str) -> ! { +pub fn error(msg: &str) -> ! { panic!("{}", send_msg("error", msg)); -} \ No newline at end of file +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 88da7cf..0a21a69 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,5 @@ +pub mod colors; pub mod help; -pub mod logger; pub mod io; -pub mod colors; -pub mod query; \ No newline at end of file +pub mod logger; +pub mod query; diff --git a/src/utils/query.rs b/src/utils/query.rs index eee7864..bf85877 100644 --- a/src/utils/query.rs +++ b/src/utils/query.rs @@ -2,25 +2,21 @@ use crate::{ language::{ lexer::{self, Lexer}, parser::parser::{self, Query}, - tokens::{self} + tokens::{self}, }, - utils::logger + utils::logger, }; - // Process query to low level components (parsed) -pub fn process_query( - query: &String -) -> Option { - // Tokenize input - let tokenized: Result = lexer::scan_tokens( - &query); +pub fn process_query(query: &String) -> Option { + // Tokenize input + let tokenized: Result = lexer::scan_tokens(&query); let tokens = match tokenized { Ok(tokenized) => { logger::debug(&format!("Tokenized String -> \n{tokenized}")); tokenized.tokens - }, + } Err(e) => { logger::warning(&e); return None; @@ -34,14 +30,13 @@ pub fn process_query( } // Parse into an Abstract Syntax Tree - let parsed: Result = parser::parse( - &tokens); + let parsed: Result = parser::parse(&tokens); let ast = match parsed { Ok(parsed) => { logger::debug(&format!("Abstract Syntax Tree -> \n{parsed}")); parsed - }, + } Err(e) => { logger::warning(&e); return None; @@ -49,4 +44,4 @@ pub fn process_query( }; return Some(ast); -} \ No newline at end of file +} diff --git a/src/validator/mod.rs b/src/validator/mod.rs index 443a259..1815b72 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -1,2 +1,2 @@ pub mod validator; -pub use validator::*; \ No newline at end of file +pub use validator::*; diff --git a/src/validator/validator.rs b/src/validator/validator.rs index 011dc02..872ff95 100644 --- a/src/validator/validator.rs +++ b/src/validator/validator.rs @@ -1,16 +1,11 @@ -use crate::{ - utils::{ - io, - colors::{ - colorize, - AnsiColor - }, - query::process_query - }, +use crate::utils::{ + colors::{AnsiColor, colorize}, + io, + query::process_query, }; /// Starts a Validator loop that accepts queries from STDIN -/// and validates queries while outputting error information +/// and validates queries while outputting error information pub fn repl_loop() { loop { // Get input @@ -18,13 +13,19 @@ pub fn repl_loop() { match process_query(&query) { Some(_) => println!("{}", colorize("Valid query!", AnsiColor::BrightGreen)), - None => println!("{}", colorize("Invalid query, see above warnings for issues!", AnsiColor::BrightRed)) + None => println!( + "{}", + colorize( + "Invalid query, see above warnings for issues!", + AnsiColor::BrightRed + ) + ), }; } } /// Validate Input Query (String) -/// +/// /// # Example /// ``` /// use eaql::validator::engine; @@ -35,6 +36,6 @@ pub fn repl_loop() { pub fn engine(query: &str) -> bool { match process_query(&query.to_string()) { Some(_) => true, - None => false + None => false, } -} \ No newline at end of file +} diff --git a/tests/transpiler_tests.rs b/tests/transpiler_tests.rs index 09f7f6f..6c693c7 100644 --- a/tests/transpiler_tests.rs +++ b/tests/transpiler_tests.rs @@ -3,41 +3,68 @@ use eaql::transpiler::engine; // Database Query Tests (Validator) // Normal #[test] -fn integration_test_db_create_normal() { +fn transpile_integration_test_db_create_normal() { // Create keyword tests - assert_eq!(engine("create database test;"), Ok("CREATE DATABASE test;".to_string())); - assert_eq!(engine("make database test."), Ok("CREATE DATABASE test;".to_string())); + assert_eq!( + engine("create database test;"), + Ok("CREATE DATABASE test;".to_string()) + ); + assert_eq!( + engine("make database test."), + Ok("CREATE DATABASE test;".to_string()) + ); } #[test] -fn integration_test_db_use_normal() { +fn transpile_integration_test_db_use_normal() { // Use keyword tests - assert_eq!(engine("use database test;"), Ok("USE DATABASE test;".to_string())); - assert_eq!(engine("enter database test;"), Ok("USE DATABASE test;".to_string())); + assert_eq!( + engine("use database test;"), + Ok("USE DATABASE test;".to_string()) + ); + assert_eq!( + engine("enter database test;"), + Ok("USE DATABASE test;".to_string()) + ); } #[test] -fn integration_test_db_show_normal() { +fn transpile_integration_test_db_show_normal() { // Show keyword tests assert_eq!(engine("show database;"), Ok("SHOW DATABASES;".to_string())); assert_eq!(engine("list databases."), Ok("SHOW DATABASES;".to_string())); } #[test] -fn integration_test_db_destroy_normal() { +fn transpile_integration_test_db_destroy_normal() { // Destroy - assert_eq!(engine("remove database db1!"), Ok("DROP DATABASE db1;".to_string())); - assert_eq!(engine("destroy database db1!"), Ok("DROP DATABASE db1;".to_string())); - assert_eq!(engine("delete database db1!"), Ok("DROP DATABASE db1;".to_string())); - + assert_eq!( + engine("remove database db1!"), + Ok("DROP DATABASE db1;".to_string()) + ); + assert_eq!( + engine("destroy database db1!"), + Ok("DROP DATABASE db1;".to_string()) + ); + assert_eq!( + engine("delete database db1!"), + Ok("DROP DATABASE db1;".to_string()) + ); + // Multiple databases tests - assert_eq!(engine("delete databases db1, db2, db3;"), Ok("DROP DATABASE db1, db2, db3;".to_string())); - assert_eq!(engine("Destroy the databases db1, db2 and db3."), Ok("DROP DATABASE db1, db2, db3;".to_string())); + assert_eq!( + engine("delete databases db1, db2, db3;"), + Ok("DROP DATABASE db1, db2, db3;".to_string()) + ); + assert_eq!( + engine("Destroy the databases db1, db2 and db3."), + Ok("DROP DATABASE db1, db2, db3;".to_string()) + ); } // Error #[test] -fn integration_test_db_create_error() { +fn transpile_integration_test_db_create_error() { // Generic Error Test assert!(engine("create database test").is_err()); assert!(engine("Create the test!").is_err()); @@ -45,7 +72,7 @@ fn integration_test_db_create_error() { } #[test] -fn integration_test_db_use_error() { +fn transpile_integration_test_db_use_error() { // Generic Error Test assert!(engine("use database test").is_err()); assert!(engine("Enter test!").is_err()); @@ -53,7 +80,7 @@ fn integration_test_db_use_error() { } #[test] -fn integration_test_db_show_error() { +fn transpile_integration_test_db_show_error() { // Generic Error Test assert!(engine("show database").is_err()); assert!(engine("show!").is_err()); @@ -61,98 +88,156 @@ fn integration_test_db_show_error() { } #[test] -fn integration_test_db_destroy_error() { +fn transpile_integration_test_db_destroy_error() { // Generic Error Test assert!(engine("delete databases db1, db2, db3").is_err()); assert!(engine("Delete db1!").is_err()); assert!(engine("Destroy the databases db1, \"db2\" and db3.").is_err()); } - // Table Accessor Query Tests (Validator) // Normal #[test] -fn integration_test_table_accessor_normal_get() { +fn transpile_integration_test_table_accessor_normal_get() { // Test "wildcard" keywords - assert_eq!(engine("get all from test_table;"), Ok("SELECT * FROM test_table;".to_string())); - assert_eq!(engine("get any from test_table;"), Ok("SELECT * FROM test_table;".to_string())); - assert_eq!(engine("get everything from test_table;"), Ok("SELECT * FROM test_table;".to_string())); - + assert_eq!( + engine("get all from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); + assert_eq!( + engine("get any from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); + assert_eq!( + engine("get everything from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); + // Test "get" keywords - assert_eq!(engine("get all from test_table;"), Ok("SELECT * FROM test_table;".to_string())); - assert_eq!(engine("retrieve all from test_table;"), Ok("SELECT * FROM test_table;".to_string())); - assert_eq!(engine("find all from test_table;"), Ok("SELECT * FROM test_table;".to_string())); + assert_eq!( + engine("get all from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); + assert_eq!( + engine("retrieve all from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); + assert_eq!( + engine("find all from test_table;"), + Ok("SELECT * FROM test_table;".to_string()) + ); // Test column listing - assert_eq!(engine("get me id and value from test_table."), Ok("SELECT id, value FROM test_table;".to_string())); - assert_eq!(engine("get me id, price, value from test_table!"), Ok("SELECT id, price, value FROM test_table;".to_string())); + assert_eq!( + engine("get me id and value from test_table."), + Ok("SELECT id, value FROM test_table;".to_string()) + ); + assert_eq!( + engine("get me id, price, value from test_table!"), + Ok("SELECT id, price, value FROM test_table;".to_string()) + ); } #[test] -fn integration_test_table_accessor_normal_filter() { +fn transpile_integration_test_table_accessor_normal_filter() { // Test "filter entrance" keywords - assert_eq!(engine("get all from test_table where id = 3;"), Ok("SELECT * FROM test_table WHERE id = 3;".to_string())); - assert_eq!(engine("get all from test_table wherever id = 3;"), Ok("SELECT * FROM test_table WHERE id = 3;".to_string())); - assert_eq!(engine("get all from test_table whenever id = 3;"), Ok("SELECT * FROM test_table WHERE id = 3;".to_string())); - + assert_eq!( + engine("get all from test_table where id = 3;"), + Ok("SELECT * FROM test_table WHERE id = 3;".to_string()) + ); + assert_eq!( + engine("get all from test_table wherever id = 3;"), + Ok("SELECT * FROM test_table WHERE id = 3;".to_string()) + ); + assert_eq!( + engine("get all from test_table whenever id = 3;"), + Ok("SELECT * FROM test_table WHERE id = 3;".to_string()) + ); + // Test different conditionals - assert_eq!(engine("get all from test_table where id = 3 and price = 2.0."), - Ok("SELECT * FROM test_table WHERE id = 3 and price = 2.0;".to_string())); - assert_eq!(engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\")!"), - Ok("SELECT * FROM test_table WHERE id = 3 or ( price <= 2 and name = \"3\" );".to_string())); + assert_eq!( + engine("get all from test_table where id = 3 and price = 2.0."), + Ok("SELECT * FROM test_table WHERE id = 3 and price = 2.0;".to_string()) + ); + assert_eq!( + engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\")!"), + Ok("SELECT * FROM test_table WHERE id = 3 or (price <= 2 and name = \"3\");".to_string()) + ); assert_eq!(engine("get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)));"), - Ok("SELECT * FROM test_table WHERE ( price < 3 or name = \"test\" and ( id = 3 or ( value < 4 and time >= 5 ) ) );".to_string())); + Ok("SELECT * FROM test_table WHERE (price < 3 or name = \"test\" and (id = 3 or (value < 4 and time >= 5)));".to_string())); } - #[test] -fn integration_test_table_accessor_normal_postprocessor() { +fn transpile_integration_test_table_accessor_normal_postprocessor() { // Test "post-processor entrance" keywords - assert_eq!(engine("get all from test_table then limit 5;"), Ok("SELECT * FROM test_table LIMIT 5;".to_string())); - assert_eq!(engine("get all from test_table afterwords limit 5;"), Ok("SELECT * FROM test_table LIMIT 5;".to_string())); - assert_eq!(engine("get all from test_table after limit 5;"), Ok("SELECT * FROM test_table LIMIT 5;".to_string())); + assert_eq!( + engine("get all from test_table then limit 5;"), + Ok("SELECT * FROM test_table LIMIT 5;".to_string()) + ); + assert_eq!( + engine("get all from test_table afterwords limit 5;"), + Ok("SELECT * FROM test_table LIMIT 5;".to_string()) + ); + assert_eq!( + engine("get all from test_table after limit 5;"), + Ok("SELECT * FROM test_table LIMIT 5;".to_string()) + ); // Test post-processor w/ filter - assert_eq!(engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\") then limit 5."), - Ok("SELECT * FROM test_table WHERE id = 3 or ( price <= 2 and name = \"3\" ) LIMIT 5;".to_string())); + assert_eq!( + engine( + "get all from test_table where id = 3 or (price <= 2 and name is \"3\") then limit 5." + ), + Ok( + "SELECT * FROM test_table WHERE id = 3 or (price <= 2 and name = \"3\") LIMIT 5;" + .to_string() + ) + ); } - #[test] -fn integration_test_table_accessor_normal_postprocessor_limit() { +fn transpile_integration_test_table_accessor_normal_postprocessor_limit() { // Test "post-processor limit" keywords - assert_eq!(engine("get all from test_table then limit 5;"), Ok("SELECT * FROM test_table LIMIT 5;".to_string())); - assert_eq!(engine("get all from test_table then limit it to 5;"), Ok("SELECT * FROM test_table LIMIT 5;".to_string())); + assert_eq!( + engine("get all from test_table then limit 5;"), + Ok("SELECT * FROM test_table LIMIT 5;".to_string()) + ); + assert_eq!( + engine("get all from test_table then limit it to 5;"), + Ok("SELECT * FROM test_table LIMIT 5;".to_string()) + ); } // Error #[test] -fn integration_test_table_accessor_error_get() { +fn transpile_integration_test_table_accessor_error_get() { // Test improper tokens assert!(engine("get all from \"test_table\";").is_err()); assert!(engine("get all from test_table").is_err()); assert!(engine("get everything in test_table;").is_err()); - + // Test invalid column listing assert!(engine("get me id and \"value\" from test_table.").is_err()); assert!(engine("get me id, price value from test_table!").is_err()); } #[test] -fn integration_test_table_accessor_error_filter() { +fn transpile_integration_test_table_accessor_error_filter() { // Test bad conditions assert!(engine("get all from test_table where id = 3").is_err()); assert!(engine("get all from test_table where id is equal to 3;").is_err()); assert!(engine("get all from test_table where id <== 3;").is_err()); - + // Test different bad conditionals - assert!(engine("get all from test_table where id = 3 or (price <= 2 and name is id)!").is_err()); + assert!( + engine("get all from test_table where id = 3 or (price <= 2 and name is id)!").is_err() + ); assert!(engine("get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));").is_err()); assert!(engine("get all from test_table where price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));").is_err()); } #[test] -fn integration_test_table_accessor_error_postprocessor() { +fn transpile_integration_test_table_accessor_error_postprocessor() { // Generic tests assert!(engine("get all from test_table then limit 5").is_err()); @@ -160,12 +245,17 @@ fn integration_test_table_accessor_error_postprocessor() { assert!(engine("get all from test_table afterwords is 5;").is_err()); // Test post-processor befor filter - assert!(engine("get all from test_table then limit 5 where id = 3 or (price <= 2 and name is \"3\").").is_err()); + assert!( + engine( + "get all from test_table then limit 5 where id = 3 or (price <= 2 and name is \"3\")." + ) + .is_err() + ); } #[test] -fn integration_test_table_accessor_error_postprocessor_limit() { +fn transpile_integration_test_table_accessor_error_postprocessor_limit() { // Test bad limit assert!(engine("get all from test_table then limit = 5;").is_err()); assert!(engine("get all from test_table then limit id;").is_err()); -} \ No newline at end of file +} diff --git a/tests/validator_tests.rs b/tests/validator_tests.rs index d699a53..f2b2f78 100644 --- a/tests/validator_tests.rs +++ b/tests/validator_tests.rs @@ -3,33 +3,33 @@ use eaql::validator::engine; // Database Query Tests (Validator) // Normal #[test] -fn integration_test_db_create_normal() { +fn validator_integration_test_db_create_normal() { // Create keyword tests assert_eq!(engine("create database test;"), true); assert_eq!(engine("make database test."), true); } #[test] -fn integration_test_db_use_normal() { +fn validator_integration_test_db_use_normal() { // Use keyword tests assert_eq!(engine("use database test;"), true); assert_eq!(engine("enter database test;"), true); } #[test] -fn integration_test_db_show_normal() { +fn validator_integration_test_db_show_normal() { // Show keyword tests assert_eq!(engine("show database;"), true); assert_eq!(engine("list databases."), true); } #[test] -fn integration_test_db_destroy_normal() { +fn validator_integration_test_db_destroy_normal() { // Destroy assert_eq!(engine("remove database db1!"), true); assert_eq!(engine("destroy database db1!"), true); assert_eq!(engine("delete database db1!"), true); - + // Multiple databases tests assert_eq!(engine("delete databases db1, db2, db3;"), true); assert_eq!(engine("Destroy the databases db1, db2 and db3."), true); @@ -37,7 +37,7 @@ fn integration_test_db_destroy_normal() { // Error #[test] -fn integration_test_db_create_error() { +fn validator_integration_test_db_create_error() { // Generic Error Test assert_eq!(engine("create database test"), false); assert_eq!(engine("Create the test!"), false); @@ -45,7 +45,7 @@ fn integration_test_db_create_error() { } #[test] -fn integration_test_db_use_error() { +fn validator_integration_test_db_use_error() { // Generic Error Test assert_eq!(engine("use database test"), false); assert_eq!(engine("Enter test!"), false); @@ -53,7 +53,7 @@ fn integration_test_db_use_error() { } #[test] -fn integration_test_db_show_error() { +fn validator_integration_test_db_show_error() { // Generic Error Test assert_eq!(engine("show database"), false); assert_eq!(engine("show!"), false); @@ -61,23 +61,22 @@ fn integration_test_db_show_error() { } #[test] -fn integration_test_db_destroy_error() { +fn validator_integration_test_db_destroy_error() { // Generic Error Test assert_eq!(engine("delete databases db1, db2, db3"), false); assert_eq!(engine("Delete db1!"), false); assert_eq!(engine("Destroy the databases db1, \"db2\" and db3."), false); } - // Table Accessor Query Tests (Validator) // Normal #[test] -fn integration_test_table_accessor_normal_get() { +fn validator_integration_test_table_accessor_normal_get() { // Test "wildcard" keywords assert_eq!(engine("get all from test_table;"), true); assert_eq!(engine("get any from test_table;"), true); assert_eq!(engine("get everything from test_table;"), true); - + // Test "get" keywords assert_eq!(engine("get all from test_table;"), true); assert_eq!(engine("retrieve all from test_table;"), true); @@ -89,33 +88,47 @@ fn integration_test_table_accessor_normal_get() { } #[test] -fn integration_test_table_accessor_normal_filter() { +fn validator_integration_test_table_accessor_normal_filter() { // Test "filter entrance" keywords assert_eq!(engine("get all from test_table where id = 3;"), true); assert_eq!(engine("get all from test_table wherever id = 3;"), true); assert_eq!(engine("get all from test_table whenever id = 3;"), true); - + // Test different conditionals - assert_eq!(engine("get all from test_table where id = 3 and price = 2.0."), true); - assert_eq!(engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\")!"), true); - assert_eq!(engine("get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)));"), true); + assert_eq!( + engine("get all from test_table where id = 3 and price = 2.0."), + true + ); + assert_eq!( + engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\")!"), + true + ); + assert_eq!( + engine( + "get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)));" + ), + true + ); } - #[test] -fn integration_test_table_accessor_normal_postprocessor() { +fn validator_integration_test_table_accessor_normal_postprocessor() { // Test "post-processor entrance" keywords assert_eq!(engine("get all from test_table then limit 5;"), true); assert_eq!(engine("get all from test_table afterwords limit 5;"), true); assert_eq!(engine("get all from test_table after limit 5;"), true); // Test post-processor w/ filter - assert_eq!(engine("get all from test_table where id = 3 or (price <= 2 and name is \"3\") then limit 5."), true); + assert_eq!( + engine( + "get all from test_table where id = 3 or (price <= 2 and name is \"3\") then limit 5." + ), + true + ); } - #[test] -fn integration_test_table_accessor_normal_postprocessor_limit() { +fn validator_integration_test_table_accessor_normal_postprocessor_limit() { // Test "post-processor limit" keywords assert_eq!(engine("get all from test_table then limit 5;"), true); assert_eq!(engine("get all from test_table then limit it to 5;"), true); @@ -123,32 +136,48 @@ fn integration_test_table_accessor_normal_postprocessor_limit() { // Error #[test] -fn integration_test_table_accessor_error_get() { +fn validator_integration_test_table_accessor_error_get() { // Test improper tokens assert_eq!(engine("get all from \"test_table\";"), false); assert_eq!(engine("get all from test_table"), false); assert_eq!(engine("get everything in test_table;"), false); - + // Test invalid column listing assert_eq!(engine("get me id and \"value\" from test_table."), false); assert_eq!(engine("get me id, price value from test_table!"), false); } #[test] -fn integration_test_table_accessor_error_filter() { +fn validator_integration_test_table_accessor_error_filter() { // Test bad conditions assert_eq!(engine("get all from test_table where id = 3"), false); - assert_eq!(engine("get all from test_table where id is equal to 3;"), false); + assert_eq!( + engine("get all from test_table where id is equal to 3;"), + false + ); assert_eq!(engine("get all from test_table where id <== 3;"), false); - + // Test different bad conditionals - assert_eq!(engine("get all from test_table where id = 3 or (price <= 2 and name is id)!"), false); - assert_eq!(engine("get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));"), false); - assert_eq!(engine("get all from test_table where price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));"), false); + assert_eq!( + engine("get all from test_table where id = 3 or (price <= 2 and name is id)!"), + false + ); + assert_eq!( + engine( + "get all from test_table where (price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));" + ), + false + ); + assert_eq!( + engine( + "get all from test_table where price < 3 or name is \"test\" and (id = 3 or (value < 4 and time >= 5)))));" + ), + false + ); } #[test] -fn integration_test_table_accessor_error_postprocessor() { +fn validator_integration_test_table_accessor_error_postprocessor() { // Generic tests assert_eq!(engine("get all from test_table then limit 5"), false); @@ -156,12 +185,17 @@ fn integration_test_table_accessor_error_postprocessor() { assert_eq!(engine("get all from test_table afterwords is 5;"), false); // Test post-processor befor filter - assert_eq!(engine("get all from test_table then limit 5 where id = 3 or (price <= 2 and name is \"3\")."), false); + assert_eq!( + engine( + "get all from test_table then limit 5 where id = 3 or (price <= 2 and name is \"3\")." + ), + false + ); } #[test] -fn integration_test_table_accessor_error_postprocessor_limit() { +fn validator_integration_test_table_accessor_error_postprocessor_limit() { // Test bad limit assert_eq!(engine("get all from test_table then limit = 5;"), false); assert_eq!(engine("get all from test_table then limit id;"), false); -} \ No newline at end of file +}