#[derive(Clone, Debug)]
pub enum CommandType {
String(String),
Number(f64),
FunctionDefinition(String, Vec<String>, Box<Command>),
Identifier(String),
Chain(Vec<Command>),
Assign(String),
Comment(String),
}
impl Display for CommandType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommandType::String(s) => write!(f, "string {}", s),
CommandType::Number(n) => write!(f, "number {}", n),
CommandType::FunctionDefinition(name, _, _) => {
write!(f, "function definition of {}", name)
}
CommandType::Identifier(v) => write!(f, "identifier {}", v),
CommandType::Chain(_) => write!(f, "chain"),
CommandType::Assign(a) => write!(f, "assignment to {}", a),
CommandType::Comment(_) => write!(f, "comment"),
}
}
}
pub type ErrorAt = (String, (usize, usize));
#[derive(Debug, Clone)]
pub struct Command {
pub kind: CommandType,
pub start: (usize, usize),
pub end: (usize, usize),
}
type TokenIter<'a> = Peekable<Iter<'a, TokenAt>>;
use std::{collections::HashSet, fmt::Display, iter::Peekable, slice::Iter};
use crate::tokenizer::{Token, TokenAt};
pub(crate) fn parse(tokens: Vec<TokenAt>) -> Result<Vec<Command>, ErrorAt> {
let mut tokens = tokens.iter().peekable();
parse_chain(&mut tokens, false)
}
fn token_to_command(token: TokenAt, kind: CommandType) -> Command {
Command {
kind,
start: token.1,
end: token.1,
}
}
fn parse_assign(tokens: &mut TokenIter, equal: TokenAt) -> Result<Command, ErrorAt> {
let name = match tokens.next() {
Some((Token::Word(name), _)) => name,
o => {
return Err((
format!(
"Expected variable name, got {}",
match o {
None => "file end".to_string(),
Some(a) => format!("{}", a.0),
}
),
equal.1,
));
}
};
Ok(Command {
kind: CommandType::Assign(name.clone()),
start: equal.1,
end: equal.1,
})
}
fn parse_command(tokens: &mut TokenIter, token: TokenAt) -> Result<Command, ErrorAt> {
if matches!(token, (Token::Assign, _)) {
return parse_assign(tokens, token);
}
match token.0 {
Token::Word(ref c) => {
let str = c.clone();
Ok(token_to_command(token, CommandType::Identifier(str)))
}
Token::String(ref s) => {
let str = s.clone();
Ok(token_to_command(token, CommandType::String(str)))
}
Token::Number(n) => Ok(token_to_command(token, CommandType::Number(n))),
Token::OpenBracket => Ok(Command {
kind: CommandType::Chain(parse_chain(tokens, true)?),
start: (0, 0),
end: (0, 0),
}),
Token::ClosingBracket => Err(("Unexpected closing parenthesis".into(), token.1)),
Token::Assign => unreachable!(),
Token::Colon => Err(("Expected function body before colon".into(), token.1)),
Token::Comment(c) => Ok(Command {
kind: CommandType::Comment(c),
start: token.1,
end: token.1,
}),
}
}
/// Parse a list of statements until the end or a closing bracket is reached.
fn parse_chain(tokens: &mut TokenIter, in_brackets: bool) -> Result<Vec<Command>, ErrorAt> {
let mut commands: Vec<Command> = Vec::new();
let mut next = tokens.peek().copied();
if next.is_some() {
tokens.next();
};
loop {
match next {
Some(token) => {
if in_brackets && matches!(token, (Token::ClosingBracket, _)) {
break;
} else {
let cmd = parse_command(tokens, token.clone())?;
commands.push(cmd)
};
if let Some((Token::Colon, at)) = tokens.peek() {
let definition = parse_function_definition(tokens, &mut commands, at)?;
commands.push(definition);
};
next = tokens.peek().copied();
if next.is_some() {
tokens.next();
};
}
None => {
if in_brackets {
next = tokens.next();
if next.is_none() {
return Err((
"Expected closing parenthesis, got file end".to_string(),
(0, 0),
));
}
} else {
break;
}
}
}
}
Ok(commands)
}
fn parse_function_definition(
tokens: &mut TokenIter,
commands: &mut Vec<Command>,
colon: &(usize, usize),
) -> Result<Command, ErrorAt> {
tokens.next();
let args;
let name_token = match commands.pop() {
Some(Command {
kind: CommandType::Chain(c),
start: _,
end: _,
}) => {
let mut found = HashSet::with_capacity(0);
args = c
.iter()
.map(|a| match a {
Command {
kind: CommandType::Identifier(c),
start,
end: _,
} => {
if found.get(c.as_str()).is_some() {
return Err((format!("Got parameter {} twice", c), *start));
};
found.insert(c.as_str());
Ok(c.to_owned())
}
o => Err((
format!("Invalid function argument name {}", o.kind),
o.start,
)),
})
.collect::<Result<Vec<String>, ErrorAt>>()?;
commands.pop()
}
o => {
args = vec![];
o
}
};
let name = match name_token {
Some(Command {
kind: CommandType::Identifier(n),
start: _,
end: _,
}) => n,
o => {
return Err((
format!(
"Expected function name before colon, got {}",
match o {
Some(e) => format!("{}", e.kind),
None => "nothing".to_string(),
}
),
(colon.0, colon.1.saturating_sub(1)),
))
} // TODO: Make this position better.
};
let body = match tokens.next() {
Some(next) => parse_command(tokens, next.clone())?,
None => {
return Err(("Expected function body, got file end".to_string(), *colon));
}
};
Ok(Command {
kind: CommandType::FunctionDefinition(name, args, Box::new(body)),
start: *colon,
end: *colon,
})
}