tinyscript 0.6.1

Tiny, C-like scripting language.
Documentation
// Copyright © 2025 Stephan Kunz
//! [`UnaryParselet`] analyzes and handles the prefix (aka unary) expressions.

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

use super::PrefixParselet;

#[derive(Clone)]
pub struct UnaryParselet;

impl PrefixParselet for UnaryParselet {
	fn parse(
		&self,
		lexer: &mut Lexer,
		parser: &mut Parser,
		chunk: &mut Chunk,
		_token: Token,
	) -> Result<(), CompilationError> {
		let token = parser.current();
		// there must be a current token
		if parser.next().kind == TokenKind::None {
			return Err(CompilationError::ExpressionExpected {
				token: "None".into(),
				pos: parser.next().line,
			});
		}
		// compile the operand
		parser.with_precedence(lexer, Precedence::Unary, chunk)?;
		match token.kind {
			TokenKind::Bang => {
				// add the logical not
				parser.emit_byte(OpCode::Not as u8, chunk);
				Ok(())
			}
			TokenKind::Minus => {
				// add the negation
				parser.emit_byte(OpCode::Negate as u8, chunk);
				Ok(())
			}
			TokenKind::Plus => {
				// do nothing
				Ok(())
			}
			TokenKind::Tilde => {
				// add the binary not
				parser.emit_byte(OpCode::BitwiseNot as u8, chunk);
				Ok(())
			}
			_ => Err(CompilationError::Unreachable {
				file: file!().into(),
				line: line!(),
			}),
		}
	}
}

#[cfg(test)]
mod tests {
	use alloc::{collections::btree_map::BTreeMap, string::String};

	use super::*;

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

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

	#[test]
	fn parse_rejects_non_unary_current_token() {
		// `UnaryParselet` is only registered for `!`, `-`, `+` and `~`, so the
		// final `_` arm is unreachable through the public `Runtime` API. Drive
		// the parser state manually so `current` holds a non-unary token
		// (an integer literal) while still passing the initial
		// `parser.next().kind == None` and `with_precedence` checks.
		let enums = BTreeMap::<String, i8>::new();
		let mut lexer = Lexer::new(&enums, "1 2");
		let mut parser = Parser::new();
		let mut chunk = Chunk::default();
		assert!(parser.advance(&mut lexer).is_ok());
		assert!(parser.advance(&mut lexer).is_ok());
		let token = parser.current();
		let result = UnaryParselet.parse(&mut lexer, &mut parser, &mut chunk, token);
		assert!(matches!(result, Err(CompilationError::Unreachable { .. })));
	}
}