tinyscript 0.6.1

Tiny, C-like scripting language.
Documentation
// Copyright © 2025 Stephan Kunz
//! [`LogicParselet`] analyzes and handles logical expressions.

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

use super::InfixParselet;

/// The specific logical/bitwise operator this parselet handles. Storing it
/// on the parselet (rather than re-matching on `parser.current().kind`) keeps
/// the dispatch in [`LogicParselet::parse`] exhaustive and removes the need
/// for defensive `Unreachable` arms.
#[derive(Clone, Copy, Debug)]
pub enum LogicKind {
	/// `&`  — bitwise AND.
	BitAnd,
	/// `&&` — short-circuiting logical AND.
	And,
	/// `^`  — bitwise XOR.
	BitXor,
	/// `||` — short-circuiting logical OR.
	Or,
	/// `|`  — bitwise OR.
	BitOr,
	/// `? :` — ternary conditional.
	Ternary,
}

impl LogicKind {
	const fn precedence(self) -> Precedence {
		match self {
			Self::BitAnd => Precedence::BitAnd,
			Self::And => Precedence::And,
			Self::BitXor => Precedence::BitXor,
			Self::Or => Precedence::Or,
			Self::BitOr => Precedence::BitOr,
			Self::Ternary => Precedence::Ternary,
		}
	}

	/// The precedence to recurse into for the right-hand side / branches.
	/// 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::BitAnd => Precedence::Equality,
			Self::And => Precedence::BitOr,
			Self::BitXor => Precedence::BitAnd,
			Self::Or => Precedence::And,
			Self::BitOr => Precedence::BitXor,
			Self::Ternary => Precedence::Or,
		}
	}
}

#[derive(Clone)]
pub struct LogicParselet {
	kind: LogicKind,
}

impl LogicParselet {
	pub const fn new(kind: LogicKind) -> Self {
		Self { kind }
	}
}

impl InfixParselet for LogicParselet {
	fn parse(
		&self,
		lexer: &mut Lexer,
		parser: &mut Parser,
		chunk: &mut Chunk,
		_token: Token,
	) -> Result<(), CompilationError> {
		let higher = self.kind.higher();
		match self.kind {
			LogicKind::BitAnd => {
				parser.with_precedence(lexer, higher, chunk)?;
				parser.emit_byte(OpCode::BitwiseAnd as u8, chunk);
				Ok(())
			}
			LogicKind::And => {
				let target_pos = parser.emit_jump(OpCode::JmpIfFalse as u8, chunk);
				parser.emit_byte(OpCode::Pop as u8, chunk);
				parser.with_precedence(lexer, higher, chunk)?;
				Parser::patch_jump(target_pos, chunk);
				Ok(())
			}
			LogicKind::BitXor => {
				parser.with_precedence(lexer, higher, chunk)?;
				parser.emit_byte(OpCode::BitwiseXor as u8, chunk);
				Ok(())
			}
			LogicKind::Or => {
				let target_pos = parser.emit_jump(OpCode::JmpIfTrue as u8, chunk);
				parser.emit_byte(OpCode::Pop as u8, chunk);
				parser.with_precedence(lexer, higher, chunk)?;
				Parser::patch_jump(target_pos, chunk);
				Ok(())
			}
			LogicKind::BitOr => {
				parser.with_precedence(lexer, higher, chunk)?;
				parser.emit_byte(OpCode::BitwiseOr as u8, chunk);
				Ok(())
			}
			LogicKind::Ternary => {
				let else_pos = parser.emit_jump(OpCode::JmpIfFalse as u8, chunk);
				// remove the decision value
				parser.emit_byte(OpCode::Pop as u8, chunk);
				// run the "true" expression
				parser.with_precedence(lexer, higher, chunk)?;
				let end_pos = parser.emit_jump(OpCode::Jmp as u8, chunk);
				Parser::patch_jump(else_pos, chunk);
				// consume the ':'
				parser.consume(lexer, TokenKind::Colon)?;
				// remove the decision value
				parser.emit_byte(OpCode::Pop as u8, chunk);
				// run the "false" expression
				parser.with_precedence(lexer, higher, chunk)?;
				Parser::patch_jump(end_pos, chunk);
				Ok(())
			}
		}
	}

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