summaryrefslogtreecommitdiff
path: root/src/ast.rs
blob: 5584975e7547877f2fd3822b9ed6abd89aa68cae (plain)
use std::iter::Peekable;
use std::sync::Arc;

use rust_decimal::Decimal;

use crate::tokens::{Lexer, Token};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Program {
	pub commands: Arc<[Command]>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Command {
	Statement(Statement),
	Expression(Expression),
	PanicMode(Arc<[Token]>),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Statement {
	pub function_name: Arc<str>,
	pub function_token: Token,
	pub args: Arc<[Expression]>,
	pub newline: Option<Token>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Expression {
	pub prefix: Arc<[Token]>,
	pub suffix: ExpressionSuffix,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExpressionSuffix {
	Nothing,
	Number { value: Decimal, token: Token },
	String { value: Arc<str>, token: Token },
	Identifier { name: Arc<str>, token: Token },
	List(List),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct List {
	pub left_paren: Token,
	pub items: Arc<[ListItem]>,
	pub right_paren: Option<Token>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListItem {
	Dot(Token),
	Expression(Expression),
}

impl Command {
	pub fn is_empty(&self) -> bool {
		if let Self::PanicMode(tokens) = self {
			tokens.is_empty()
		} else {
			false
		}
	}

	pub fn expression(&self) -> Option<&Expression> {
		if let Self::Expression(expr) = self {
			Some(expr)
		} else {
			None
		}
	}

	pub fn statement(&self) -> Option<&Statement> {
		if let Self::Statement(statement) = self {
			Some(statement)
		} else {
			None
		}
	}
}

impl Expression {
	pub fn is_quoted(&self) -> bool {
		self.prefix.len() == 1 && self.prefix[0].is_apostrophe()
	}
}

impl ExpressionSuffix {
	pub fn is_empty(&self) -> bool {
		matches!(self, Self::Nothing)
	}

	pub fn number(&self) -> Option<&Decimal> {
		if let Self::Number { value, .. } = self {
			Some(value)
		} else {
			None
		}
	}

	pub fn string(&self) -> Option<&Arc<str>> {
		if let Self::String { value, .. } = self {
			Some(value)
		} else {
			None
		}
	}

	pub fn identifier(&self) -> Option<&Arc<str>> {
		if let Self::Identifier { name, .. } = self {
			Some(name)
		} else {
			None
		}
	}

	pub fn list(&self) -> Option<&List> {
		if let Self::List(list) = self {
			Some(list)
		} else {
			None
		}
	}
}

impl List {
	pub fn contains_dot(&self) -> bool {
		self.items.iter().any(|item| item.dot().is_some())
	}
}

impl ListItem {
	pub fn dot(&self) -> Option<&Token> {
		if let Self::Dot(token) = self {
			Some(token)
		} else {
			None
		}
	}

	pub fn expression(&self) -> Option<&Expression> {
		if let Self::Expression(expression) = self {
			Some(expression)
		} else {
			None
		}
	}
}

fn parse_many<R>(
	lexer: &mut Peekable<Lexer>,
	predicate: fn(&Token) -> bool,
	parser: fn(&mut Peekable<Lexer>) -> Result<R, String>,
) -> Result<Vec<R>, String> {
	let mut items = Vec::new();
	while let Some(next) = lexer.peek() {
		if !predicate(next) {
			break;
		}

		items.push(parser(lexer)?)
	}

	Ok(items)
}

fn parse_if<R>(
	lexer: &mut Peekable<Lexer>,
	predicate: fn(&Token) -> bool,
	parser: fn(&mut Peekable<Lexer>) -> Result<R, String>,
) -> Option<Result<R, String>> {
	let next = lexer.peek()?;
	predicate(next).then(|| parser(lexer))
}

fn skip_whitespace_and_comments(lexer: &mut Peekable<Lexer>) {
	while lexer
		.next_if(|token| token.is_comment() || token.is_whitespace())
		.is_some()
	{}
}

fn skip_spaces_and_comments(lexer: &mut Peekable<Lexer>) {
	while lexer
		.next_if(|token: &Token| {
			(token.is_whitespace() && !token.contains_newline()) || token.is_comment()
		})
		.is_some()
	{}
}

fn skip_non_expression_tokens(lexer: &mut Peekable<Lexer>) -> Vec<Token> {
	let mut tokens = Vec::new();
	while let Some(token) = lexer.next_if(|token| !begins_expression(token)) {
		tokens.push(token);
	}

	tokens
}

fn begins_expression(token: &Token) -> bool {
	match &token.ty {
		crate::tokens::TokenType::Whitespace(_) => false,
		crate::tokens::TokenType::LineComment(_) => false,
		crate::tokens::TokenType::BlockComment { .. } => false,
		crate::tokens::TokenType::LeftParenthesis => true,
		crate::tokens::TokenType::RightParenthesis => false,
		crate::tokens::TokenType::Apostrophe => true,
		crate::tokens::TokenType::Pound => true,
		crate::tokens::TokenType::Dot => false,
		crate::tokens::TokenType::Identifier(_) => true,
		crate::tokens::TokenType::String { .. } => true,
		crate::tokens::TokenType::Number(_) => true,
	}
}

fn begins_list_item(token: &Token) -> bool {
	begins_expression(token) || token.is_dot()
}

pub fn parse_program(lexer: &mut Peekable<Lexer>) -> Result<Program, String> {
	skip_whitespace_and_comments(lexer);
	let mut commands = parse_many(lexer, |_| true, parse_command)?;
	if commands.last().map(|c| c.is_empty()).unwrap_or(false) {
		commands.pop();
	}

	let commands = commands.into();
	Ok(Program { commands })
}

fn parse_command(lexer: &mut Peekable<Lexer>) -> Result<Command, String> {
	skip_whitespace_and_comments(lexer);
	if let Some(result) = parse_if(lexer, |token| token.is_identifier(), parse_statement) {
		result.map(Command::Statement)
	} else if let Some(result) = parse_if(lexer, begins_expression, parse_expression) {
		result.map(Command::Expression)
	} else {
		Ok(Command::PanicMode(skip_non_expression_tokens(lexer).into()))
	}
}

fn parse_statement(lexer: &mut Peekable<Lexer>) -> Result<Statement, String> {
	skip_whitespace_and_comments(lexer);
	let function_token = lexer
		.next()
		.ok_or_else(|| String::from("expected a function name"))?;
	let function_name = function_token
		.identifier()
		.ok_or_else(|| String::from("expected the function name to be an identifier"))?
		.into();
	skip_spaces_and_comments(lexer);
	let args = parse_many(lexer, begins_expression, parse_expression)?.into();
	skip_spaces_and_comments(lexer);
	let newline = lexer.next_if(|token| token.contains_newline());

	Ok(Statement {
		function_name,
		function_token,
		args,
		newline,
	})
}

fn parse_expression(lexer: &mut Peekable<Lexer>) -> Result<Expression, String> {
	skip_whitespace_and_comments(lexer);
	let prefix = parse_many(
		lexer,
		|token| token.is_pound() || token.is_apostrophe(),
		|parser| {
			let result = parser.next().ok_or(String::new());
			skip_whitespace_and_comments(parser);
			result
		},
	)?
	.into();
	skip_whitespace_and_comments(lexer);
	let suffix = parse_expression_suffix(lexer)?;
	skip_spaces_and_comments(lexer);

	Ok(Expression { prefix, suffix })
}

fn parse_expression_suffix(lexer: &mut Peekable<Lexer>) -> Result<ExpressionSuffix, String> {
	skip_whitespace_and_comments(lexer);
	if let Some(list) = parse_if(lexer, |token| token.is_left_parenthesis(), parse_list) {
		list.map(ExpressionSuffix::List)
	} else if let Some(identifier) = lexer.next_if(|token| token.is_identifier()) {
		Ok(ExpressionSuffix::Identifier {
			name: identifier
				.identifier()
				.ok_or_else(|| String::from("we just checked for an identifier"))?
				.into(),
			token: identifier,
		})
	} else if let Some(string) = lexer.next_if(|token| token.is_string()) {
		Ok(ExpressionSuffix::String {
			value: string
				.computed_string()
				.ok_or_else(|| String::from("we just checked for an string"))?
				.into(),
			token: string,
		})
	} else if let Some(number) = lexer.next_if(|token| token.is_number()) {
		Ok(ExpressionSuffix::Number {
			value: *number
				.number()
				.ok_or_else(|| String::from("we just checked for a number"))?,
			token: number,
		})
	} else {
		Ok(ExpressionSuffix::Nothing)
	}
}

fn parse_list(lexer: &mut Peekable<Lexer>) -> Result<List, String> {
	skip_whitespace_and_comments(lexer);
	let left_paren = lexer
		.next_if(|token| token.is_left_parenthesis())
		.ok_or_else(|| String::from("Unexpected token. Expected a left parenthesis"))?;
	skip_whitespace_and_comments(lexer);
	let items = parse_many(lexer, begins_list_item, parse_list_item)?.into();
	skip_whitespace_and_comments(lexer);
	let right_paren = lexer.next_if(|token| token.is_right_parenthesis());

	Ok(List {
		left_paren,
		items,
		right_paren,
	})
}

fn parse_list_item(lexer: &mut Peekable<Lexer>) -> Result<ListItem, String> {
	skip_whitespace_and_comments(lexer);
	if let Some(dot) = lexer.next_if(|token| token.is_dot()) {
		skip_whitespace_and_comments(lexer);
		Ok(ListItem::Dot(dot))
	} else {
		let result = parse_expression(lexer).map(ListItem::Expression);
		skip_whitespace_and_comments(lexer);
		result
	}
}