bean-script 0.2.12

Simple scripting language for easy use in other projects.
Documentation
use std::cell::Cell;

use crate::{
	error::{Error, ErrorSource},
	lexer::Token,
};

#[derive(Debug, Clone)]
pub enum Node {
	FnCall {
		name: String,
		parameters: Vec<Box<PosNode>>,
		body_fn: Option<Box<PosNode>>,
	},
	Scope {
		body: Vec<Box<PosNode>>,
	},
	ParameterBlock {
		body: Vec<Box<PosNode>>,
	},
	Program {
		body: Vec<Box<PosNode>>,
	},
	FnAccess {
		target: Box<PosNode>,
		call: Box<PosNode>,
	},
	Boolean(bool),
	Number(f64),
	String(String),
	Name(String),
	None,
}

#[derive(Debug, Clone)]
pub struct PosNode {
	pub node: Node,
	pub ln: usize,
}

pub fn parse(tokens: Vec<Token>) -> Result<PosNode, Error> {
	let i = Cell::new(0usize);
	let line = Cell::new(1usize);
	let mut body = Vec::new();

	let next = || &tokens[i.replace(i.get() + 1)];
	let peek = || &tokens[i.get()];
	let get_ln = || line.get();
	let new_ln = || line.replace(line.get() + 1);

	fn parse_token<'a>(
		mut token: &'a Token,
		next: &dyn Fn() -> &'a Token,
		peek: &dyn Fn() -> &'a Token,
		get_ln: &dyn Fn() -> usize,
		new_ln: &dyn Fn() -> usize,
		prevent_accessor: bool,
	) -> Result<PosNode, Error> {
		while let Token::LineBreak = token {
			new_ln();
			token = next();
		}

		let node: Node = match token {
			Token::FnName(name) => {
				let mut parameters = Vec::new();
				let mut body_fn = None;

				if let Token::ArgOpen = peek() {
					next();

					loop {
						while let Token::LineBreak = peek() {
							new_ln();
							next();
						}

						if let Token::ArgClose = peek() {
							break;
						} else if let Token::EOF = peek() {
							return Err(Error::new(
								"Unexpected end of file. (expected ')')",
								ErrorSource::Line(get_ln()),
							));
						}

						let mut body = Vec::new();

						loop {
							match peek() {
								Token::ArgSeparator | Token::ArgClose | Token::EOF => {
									break;
								}
								_ => (),
							}
							body.push(Box::new(parse_token(
								next(),
								&next,
								&peek,
								&get_ln,
								&new_ln,
								false,
							)?));
						}

						if let Token::ArgSeparator = peek() {
							next();
						}

						parameters.push(Box::new(PosNode {
							node: Node::ParameterBlock { body },
							ln: get_ln(),
						}));
					}

					if let Token::ArgClose = peek() {
						next();
					}
				}

				while let Token::LineBreak = peek() {
					new_ln();
					next();
				}

				if let Token::FnBody = peek() {
					next();
					body_fn = Some(Box::new(parse_token(
						next(),
						&next,
						&peek,
						&get_ln,
						&new_ln,
						false,
					)?));
				}

				while let Token::LineBreak = peek() {
					new_ln();
					next();
				}

				if (if let Token::Accessor = peek() {
					true
				} else {
					false
				}) && !prevent_accessor
				{
					let mut node = PosNode {
						node: Node::FnCall {
							name: name.clone(),
							parameters,
							body_fn,
						},
						ln: get_ln(),
					};

					while let Token::Accessor = peek() {
						next();

						while let Token::LineBreak = peek() {
							new_ln();
							next();
						}

						node = PosNode {
							node: Node::FnAccess {
								target: Box::new(node),
								call: Box::new(parse_token(
									next(),
									&next,
									&peek,
									&get_ln,
									&new_ln,
									true,
								)?),
							},
							ln: get_ln(),
						};
					}

					node.node
				} else {
					Node::FnCall {
						name: name.clone(),
						parameters,
						body_fn,
					}
				}
			}
			Token::FnBody => {
				return Err(Error::new(
					"Unexpected function body.",
					ErrorSource::Line(get_ln()),
				))
			}
			Token::ArgSeparator => {
				return Err(Error::new("Unexpected comma.", ErrorSource::Line(get_ln())));
			}
			Token::ArgOpen => {
				let mut body = Vec::new();

				loop {
					while let Token::LineBreak = peek() {
						new_ln();
						next();
					}

					if let Token::ArgClose = peek() {
						break;
					} else if let Token::EOF = peek() {
						return Err(Error::new(
							"Unexpected end of file. (expected ')')",
							ErrorSource::Line(get_ln()),
						));
					}
					body.push(Box::new(parse_token(
						next(),
						&next,
						&peek,
						&get_ln,
						&new_ln,
						false,
					)?));
				}

				while let Token::LineBreak = peek() {
					new_ln();
					next();
				}
				if let Token::ArgClose = peek() {
					next();
				}

				Node::ParameterBlock { body }
			}
			Token::ArgClose => {
				return Err(Error::new(
					"Unexpected closing parentheses.",
					ErrorSource::Line(get_ln()),
				))
			}
			Token::ScopeOpen => {
				let mut body = Vec::new();

				loop {
					while let Token::LineBreak = peek() {
						new_ln();
						next();
					}

					if let Token::ScopeClose = peek() {
						break;
					} else if let Token::EOF = peek() {
						return Err(Error::new(
							"Unexpected end of file. (expected '}')",
							ErrorSource::Line(get_ln()),
						));
					}
					body.push(Box::new(parse_token(
						next(),
						&next,
						&peek,
						&get_ln,
						&new_ln,
						false,
					)?));
				}

				if let Token::ScopeClose = peek() {
					next();
				}

				Node::Scope { body }
			}
			Token::ScopeClose => {
				return Err(Error::new(
					"Unexpected closing brace.",
					ErrorSource::Line(get_ln()),
				))
			}
			Token::Accessor => {
				return Err(Error::new(
					"Unexpected dot operator.",
					ErrorSource::Line(get_ln()),
				))
			}
			Token::Boolean(v) => Node::Boolean(*v),
			Token::Number(v) => Node::Number(*v),
			Token::String(v) => Node::String(v.clone()),
			Token::Name(v) => Node::Name(v.clone()),
			Token::None => Node::None,
			Token::EOF => {
				return Err(Error::new(
					"Unexpected end of input. (How did this happen?)",
					ErrorSource::Line(get_ln()),
				))
			}
			Token::LineBreak => {
				return Err(Error::new(
					"Unexpected line break. (How did this happen?)",
					ErrorSource::Line(get_ln()),
				))
			}
		};

		return Ok(PosNode { node, ln: get_ln() });
	}

	while i.get() < tokens.len() - 1 {
		body.push(Box::new(parse_token(
			next(),
			&next,
			&peek,
			&get_ln,
			&new_ln,
			false,
		)?));
	}

	Ok(PosNode {
		node: Node::Program { body },
		ln: 0,
	})
}