tinyscript 0.6.1

Tiny, C-like scripting language.
Documentation
// Copyright © 2025 Stephan Kunz
//! [`BinaryParselet`] analyzses and handles the binary expressions.

use crate::{
	compilation::{Lexer, Parser, error::CompilationError, precedence::Precedence, token::Token},
	execution::{Chunk, op_code::OpCode},
};

use super::InfixParselet;

/// The specific binary operator this parselet handles. Storing it on the
/// parselet (rather than re-matching on `parser.current().kind`) keeps the
/// dispatch in [`BinaryParselet::parse`] exhaustive and removes the need for
/// defensive `Unreachable` arms.
#[derive(Clone, Copy, Debug)]
pub enum BinaryKind {
	/// `!=`
	BangEqual,
	/// `==`
	EqualEqual,
	/// `>`
	Greater,
	/// `>=`
	GreaterEqual,
	/// `<`
	Less,
	/// `<=`
	LessEqual,
	/// `+`
	Plus,
	/// `-`
	Minus,
	/// `*`
	Star,
	/// `/`
	Slash,
}

impl BinaryKind {
	const fn precedence(self) -> Precedence {
		match self {
			Self::BangEqual
			| Self::EqualEqual
			| Self::GreaterEqual
			| Self::LessEqual => Precedence::Equality,
			Self::Greater | Self::Less => Precedence::Comparison,
			Self::Plus | Self::Minus => Precedence::Term,
			Self::Star | Self::Slash => Precedence::Factor,
		}
	}

	/// The precedence to recurse into for the right-hand side. Pre-computed
	/// here so the parselet never has to handle a `None` from
	/// `Precedence::next_higher()` at parse time.
	const fn higher(self) -> Precedence {
		match self {
			Self::BangEqual
			| Self::EqualEqual
			| Self::GreaterEqual
			| Self::LessEqual => Precedence::Comparison,
			Self::Greater | Self::Less => Precedence::Term,
			Self::Plus | Self::Minus => Precedence::Factor,
			Self::Star | Self::Slash => Precedence::Unary,
		}
	}
}

#[derive(Clone)]
pub struct BinaryParselet {
	kind: BinaryKind,
}

impl BinaryParselet {
	pub const fn new(kind: BinaryKind) -> Self {
		Self { kind }
	}
}

impl InfixParselet for BinaryParselet {
	fn parse(
		&self,
		lexer: &mut Lexer,
		parser: &mut Parser,
		chunk: &mut Chunk,
		_token: Token,
	) -> Result<(), CompilationError> {
		parser.with_precedence(lexer, self.kind.higher(), chunk)?;
		match self.kind {
			BinaryKind::BangEqual => {
				parser.emit_bytes(OpCode::Equal as u8, OpCode::Not as u8, chunk);
			}
			BinaryKind::EqualEqual => {
				parser.emit_byte(OpCode::Equal as u8, chunk);
			}
			BinaryKind::Greater => {
				parser.emit_byte(OpCode::Greater as u8, chunk);
			}
			BinaryKind::GreaterEqual => {
				parser.emit_bytes(OpCode::Less as u8, OpCode::Not as u8, chunk);
			}
			BinaryKind::Less => {
				parser.emit_byte(OpCode::Less as u8, chunk);
			}
			BinaryKind::LessEqual => {
				parser.emit_bytes(OpCode::Greater as u8, OpCode::Not as u8, chunk);
			}
			BinaryKind::Plus => {
				parser.emit_byte(OpCode::Add as u8, chunk);
			}
			BinaryKind::Minus => {
				parser.emit_byte(OpCode::Subtract as u8, chunk);
			}
			BinaryKind::Star => {
				parser.emit_byte(OpCode::Multiply as u8, chunk);
			}
			BinaryKind::Slash => {
				parser.emit_byte(OpCode::Divide as u8, chunk);
			}
		}
		Ok(())
	}

	fn get_precedence(&self) -> Precedence {
		self.kind.precedence()
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	// check, that the auto traits are available
	const fn is_normal<T: Sized + Send + Sync>() {}

	#[test]
	const fn normal_types() {
		is_normal::<&BinaryParselet>();
		is_normal::<BinaryParselet>();
	}
}