use gitql_ast::statement::{IntoStatement, Statement};
use crate::diagnostic::Diagnostic;
use crate::parser::{calculate_safe_location, consume_token_or_error};
use crate::token::{Token, TokenKind};
pub(crate) fn parse_into_statement(
tokens: &[Token],
position: &mut usize,
) -> Result<Statement, Box<Diagnostic>> {
*position += 1;
if *position >= tokens.len()
|| (tokens[*position].kind != TokenKind::Outfile
&& tokens[*position].kind != TokenKind::Dumpfile)
{
return Err(Diagnostic::error(
"Expect Keyword `OUTFILE` or `DUMPFILE` after keyword `INTO`",
)
.with_location(calculate_safe_location(tokens, *position))
.as_boxed());
}
let file_format_kind = &tokens[*position].kind;
*position += 1;
if *position >= tokens.len() || !matches!(tokens[*position].kind, TokenKind::String(_)) {
return Err(Diagnostic::error(
"Expect String literal as file path after OUTFILE or DUMPFILE keyword",
)
.with_location(calculate_safe_location(tokens, *position))
.as_boxed());
}
let file_path = &tokens[*position].to_string();
*position += 1;
let is_dump_file = *file_format_kind == TokenKind::Dumpfile;
let mut lines_terminated = if is_dump_file { "" } else { "\n" }.to_string();
let mut lines_terminated_used = false;
let mut fields_terminated = if is_dump_file { "" } else { "," }.to_string();
let mut fields_terminated_used = false;
let mut enclosed = String::new();
let mut enclosed_used = false;
while *position < tokens.len() {
let token = &tokens[*position];
if token.kind == TokenKind::Lines {
if is_dump_file {
return Err(Diagnostic::error(
"`LINES TERMINATED` option can't be used with INTO DUMPFILE",
)
.add_help("To customize the format replace `DUMPFILE` with `OUTFILE` option")
.with_location(tokens[*position].location)
.as_boxed());
}
if lines_terminated_used {
return Err(
Diagnostic::error("You already used `LINES TERMINATED` option")
.with_location(tokens[*position].location)
.as_boxed(),
);
}
*position += 1;
consume_token_or_error(
tokens,
position,
TokenKind::Terminated,
"Expect `TERMINATED` keyword after `LINES` keyword",
)?;
consume_token_or_error(
tokens,
position,
TokenKind::By,
"Expect `BY` after `TERMINATED` keyword",
)?;
if *position >= tokens.len() || !matches!(tokens[*position].kind, TokenKind::String(_))
{
return Err(Diagnostic::error(
"Expect String literal as lines terminated value after BY keyword",
)
.with_location(calculate_safe_location(tokens, *position))
.as_boxed());
}
lines_terminated = tokens[*position].to_string();
lines_terminated_used = true;
*position += 1;
continue;
}
if token.kind == TokenKind::Fields {
if is_dump_file {
return Err(Diagnostic::error(
"`FIELDS TERMINATED` option can't be used with INTO DUMPFILE",
)
.add_help("To customize the format replace `DUMPFILE` with `OUTFILE` option")
.with_location(tokens[*position].location)
.as_boxed());
}
if fields_terminated_used {
return Err(
Diagnostic::error("You already used `FIELDS TERMINATED` option")
.with_location(tokens[*position].location)
.as_boxed(),
);
}
*position += 1;
consume_token_or_error(
tokens,
position,
TokenKind::Terminated,
"Expect `TERMINATED` keyword after `LINES` keyword",
)?;
consume_token_or_error(
tokens,
position,
TokenKind::By,
"Expect `BY` after `TERMINATED` keyword",
)?;
if *position >= tokens.len() || !matches!(tokens[*position].kind, TokenKind::String(_))
{
return Err(Diagnostic::error(
"Expect String literal as Field terminated value after BY keyword",
)
.with_location(calculate_safe_location(tokens, *position))
.as_boxed());
}
fields_terminated = tokens[*position].to_string();
fields_terminated_used = true;
*position += 1;
continue;
}
if token.kind == TokenKind::Enclosed {
if is_dump_file {
return Err(Diagnostic::error(
"`ENCLOSED` option can't be used with INTO DUMPFILE",
)
.add_help("To customize the format replace `DUMPFILE` with `OUTFILE` option")
.with_location(tokens[*position].location)
.as_boxed());
}
if enclosed_used {
return Err(Diagnostic::error("You already used ENCLOSED option")
.with_location(tokens[*position].location)
.as_boxed());
}
*position += 1;
if *position >= tokens.len() || !matches!(tokens[*position].kind, TokenKind::String(_))
{
return Err(Diagnostic::error(
"Expect String literal as enclosed value after ENCLOSED keyword",
)
.with_location(calculate_safe_location(tokens, *position))
.as_boxed());
}
enclosed = tokens[*position].to_string();
enclosed_used = true;
*position += 1;
continue;
}
break;
}
Ok(Statement::Into(IntoStatement {
file_path: file_path.to_string(),
lines_terminated,
fields_terminated,
enclosed,
}))
}