Begin adding unary operators

This commit is contained in:
Jeff 2024-08-12 05:44:05 -04:00
parent 74cfef1832
commit 2c374a1cd7
9 changed files with 284 additions and 19 deletions

View File

@ -37,13 +37,19 @@ pub enum Statement {
// A sequence of statements // A sequence of statements
Block(Vec<Node<Statement>>), Block(Vec<Node<Statement>>),
// Logic, math and comparison expressions // Assignment, logic, math and comparison expressions with two operands
BinaryOperation { BinaryOperation {
left: Box<Node<Statement>>, left: Box<Node<Statement>>,
operator: Node<BinaryOperator>, operator: Node<BinaryOperator>,
right: Box<Node<Statement>>, right: Box<Node<Statement>>,
}, },
// Logic and math expressions with one operand
UnaryOperation {
operator: Node<UnaryOperator>,
operand: Box<Node<Statement>>,
},
// Function calls // Function calls
BuiltInFunctionCall { BuiltInFunctionCall {
function: BuiltInFunction, function: BuiltInFunction,
@ -152,6 +158,10 @@ impl Statement {
} }
Statement::Nil(_) => None, Statement::Nil(_) => None,
Statement::PropertyAccess(_, _) => None, Statement::PropertyAccess(_, _) => None,
Statement::UnaryOperation { operator, operand } => match operator.inner {
UnaryOperator::Negate => Some(operand.inner.expected_type(context)?),
UnaryOperator::Not => Some(Type::Boolean),
},
Statement::While { .. } => None, Statement::While { .. } => None,
} }
} }
@ -331,6 +341,9 @@ impl Display for Statement {
} }
Statement::Nil(node) => write!(f, "{node};"), Statement::Nil(node) => write!(f, "{node};"),
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
Statement::UnaryOperation { operator, operand } => {
write!(f, "{operator}{operand}")
}
Statement::While { condition, body } => { Statement::While { condition, body } => {
write!(f, "while {condition} {body}") write!(f, "while {condition} {body}")
} }
@ -383,3 +396,18 @@ impl Display for BinaryOperator {
} }
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum UnaryOperator {
Negate,
Not,
}
impl Display for UnaryOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
UnaryOperator::Negate => write!(f, "-"),
UnaryOperator::Not => write!(f, "!"),
}
}
}

View File

@ -11,8 +11,8 @@ use std::{
}; };
use crate::{ use crate::{
abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, Context, DustError, Node, Span, abstract_tree::{BinaryOperator, UnaryOperator},
Statement, Type, parse, AbstractSyntaxTree, Context, DustError, Node, Span, Statement, Type,
}; };
/// Analyzes the abstract syntax tree for errors. /// Analyzes the abstract syntax tree for errors.
@ -352,6 +352,31 @@ impl<'a> Analyzer<'a> {
} }
} }
} }
Statement::UnaryOperation { operator, operand } => {
self.analyze_statement(operand)?;
if let UnaryOperator::Negate = operator.inner {
if let Some(Type::Integer | Type::Float | Type::Number) =
operand.inner.expected_type(self.context)
{
// Operand is valid
} else {
return Err(AnalyzerError::ExpectedBoolean {
actual: operand.as_ref().clone(),
});
}
}
if let UnaryOperator::Not = operator.inner {
if let Some(Type::Boolean) = operand.inner.expected_type(self.context) {
// Operand is valid
} else {
return Err(AnalyzerError::ExpectedBoolean {
actual: operand.as_ref().clone(),
});
}
}
}
Statement::While { condition, body } => { Statement::While { condition, body } => {
self.analyze_statement(condition)?; self.analyze_statement(condition)?;
self.analyze_statement(body)?; self.analyze_statement(body)?;

View File

@ -133,7 +133,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn context_removes_used_variables() { fn context_removes_variables() {
env_logger::builder().is_test(true).try_init().unwrap(); env_logger::builder().is_test(true).try_init().unwrap();
let source = " let source = "
@ -150,7 +150,7 @@ mod tests {
} }
#[test] #[test]
fn context_removes_variables_after_loop() { fn garbage_collector_does_not_break_loops() {
let source = " let source = "
y = 1 y = 1
z = 0 z = 0

View File

@ -253,6 +253,11 @@ impl Lexer {
}); });
} }
} }
'!' => {
self.position += 1;
(Token::Bang, (self.position - 1, self.position))
}
_ => { _ => {
self.position += 1; self.position += 1;
@ -479,6 +484,41 @@ impl Display for LexError {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn negate_expression() {
let input = "x = -42; -x";
assert_eq!(
lex(input),
Ok(vec![
(Token::Identifier("x"), (0, 1)),
(Token::Equal, (2, 3)),
(Token::Integer("-42"), (4, 7)),
(Token::Semicolon, (7, 8)),
(Token::Minus, (9, 10)),
(Token::Identifier("x"), (10, 11)),
(Token::Eof, (11, 11))
])
);
}
#[test]
fn not_expression() {
let input = "!true; !false";
assert_eq!(
lex(input),
Ok(vec![
(Token::Bang, (0, 1)),
(Token::Boolean("true"), (1, 5)),
(Token::Semicolon, (5, 6)),
(Token::Bang, (7, 8)),
(Token::Boolean("false"), (8, 13)),
(Token::Eof, (13, 13))
])
);
}
#[test] #[test]
fn if_else() { fn if_else() {
let input = "if x < 10 { x + 1 } else { x }"; let input = "if x < 10 { x + 1 } else { x }";

View File

@ -14,7 +14,7 @@ pub mod r#type;
pub mod value; pub mod value;
pub mod vm; pub mod vm;
pub use abstract_tree::{AbstractSyntaxTree, BinaryOperator, Node, Statement}; pub use abstract_tree::{AbstractSyntaxTree, BinaryOperator, Node, Statement, UnaryOperator};
pub use analyzer::{analyze, Analyzer, AnalyzerError}; pub use analyzer::{analyze, Analyzer, AnalyzerError};
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError}; pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
pub use context::{Context, VariableData}; pub use context::{Context, VariableData};

View File

@ -13,7 +13,7 @@ use std::{
use crate::{ use crate::{
AbstractSyntaxTree, BinaryOperator, BuiltInFunction, DustError, Identifier, LexError, Lexer, AbstractSyntaxTree, BinaryOperator, BuiltInFunction, DustError, Identifier, LexError, Lexer,
Node, Span, Statement, Token, TokenOwned, Value, Node, Span, Statement, Token, TokenOwned, UnaryOperator, Value,
}; };
/// Parses the input into an abstract syntax tree. /// Parses the input into an abstract syntax tree.
@ -163,6 +163,34 @@ impl<'src> Parser<'src> {
fn parse_primary(&mut self) -> Result<Node<Statement>, ParseError> { fn parse_primary(&mut self) -> Result<Node<Statement>, ParseError> {
match self.current { match self.current {
(Token::Bang, position) => {
self.next_token()?;
let operand = Box::new(self.parse_statement(0)?);
let operand_end = operand.position.1;
Ok(Node::new(
Statement::UnaryOperation {
operator: Node::new(UnaryOperator::Not, position),
operand,
},
(position.0, operand_end),
))
}
(Token::Minus, position) => {
self.next_token()?;
let operand = Box::new(self.parse_statement(0)?);
let operand_end = operand.position.1;
Ok(Node::new(
Statement::UnaryOperation {
operator: Node::new(UnaryOperator::Negate, position),
operand,
},
(position.0, operand_end),
))
}
(Token::Boolean(text), position) => { (Token::Boolean(text), position) => {
self.next_token()?; self.next_token()?;
@ -799,10 +827,83 @@ impl Display for ParseError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{abstract_tree::BinaryOperator, Identifier}; use crate::{abstract_tree::BinaryOperator, Identifier, UnaryOperator};
use super::*; use super::*;
#[test]
fn negate_variable() {
let input = "a = 1; -a";
assert_eq!(
parse(input),
Ok(AbstractSyntaxTree {
nodes: [
Node::new(
Statement::Nil(Box::new(Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(
Statement::Identifier(Identifier::new("a")),
(0, 1)
)),
operator: Node::new(BinaryOperator::Assign, (2, 3)),
right: Box::new(Node::new(
Statement::Constant(Value::integer(1)),
(4, 5)
)),
},
(0, 5)
))),
(0, 5)
),
Node::new(
Statement::UnaryOperation {
operator: Node::new(UnaryOperator::Negate, (0, 1)),
operand: Box::new(Node::new(
Statement::Identifier(Identifier::new("a")),
(1, 2)
)),
},
(0, 2)
)
]
.into()
})
);
}
#[test]
fn negate_expression() {
let input = "-(1 + 1)";
assert_eq!(
parse(input),
Ok(AbstractSyntaxTree {
nodes: [Node::new(
Statement::UnaryOperation {
operator: Node::new(UnaryOperator::Negate, (0, 1)),
operand: Box::new(Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(
Statement::Constant(Value::integer(1)),
(2, 3)
)),
operator: Node::new(BinaryOperator::Add, (4, 5)),
right: Box::new(Node::new(
Statement::Constant(Value::integer(1)),
(6, 7)
)),
},
(1, 8)
)),
},
(0, 8)
)]
.into()
})
);
}
#[test] #[test]
fn r#if() { fn r#if() {
let input = "if x { y }"; let input = "if x { y }";

View File

@ -28,6 +28,7 @@ pub enum Token<'src> {
WriteLine, WriteLine,
// Symbols // Symbols
Bang,
Comma, Comma,
Dot, Dot,
DoubleAmpersand, DoubleAmpersand,
@ -56,6 +57,7 @@ pub enum Token<'src> {
impl<'src> Token<'src> { impl<'src> Token<'src> {
pub fn to_owned(&self) -> TokenOwned { pub fn to_owned(&self) -> TokenOwned {
match self { match self {
Token::Bang => TokenOwned::Bang,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Comma => TokenOwned::Comma, Token::Comma => TokenOwned::Comma,
Token::Dot => TokenOwned::Dot, Token::Dot => TokenOwned::Dot,
@ -104,6 +106,7 @@ impl<'src> Token<'src> {
Token::Integer(integer_text) => integer_text, Token::Integer(integer_text) => integer_text,
Token::String(text) => text, Token::String(text) => text,
Token::Bang => "!",
Token::Comma => ",", Token::Comma => ",",
Token::Dot => ".", Token::Dot => ".",
Token::DoubleAmpersand => "&&", Token::DoubleAmpersand => "&&",
@ -147,18 +150,19 @@ impl<'src> Token<'src> {
pub fn precedence(&self) -> u8 { pub fn precedence(&self) -> u8 {
match self { match self {
Token::Dot => 12, Token::Dot => 9,
Token::Star | Token::Slash | Token::Percent => 10, Token::Star | Token::Slash | Token::Percent => 8,
Token::Plus | Token::Minus => 9, Token::Minus => 7,
Token::Plus => 6,
Token::DoubleEqual Token::DoubleEqual
| Token::Less | Token::Less
| Token::LessEqual | Token::LessEqual
| Token::Greater | Token::Greater
| Token::GreaterEqual => 8, | Token::GreaterEqual => 5,
Token::DoubleAmpersand => 7, Token::DoubleAmpersand => 4,
Token::DoublePipe => 6, Token::DoublePipe => 3,
Token::Equal | Token::PlusEqual => 5, Token::Equal | Token::PlusEqual => 2,
Token::Semicolon => 4, Token::Semicolon => 1,
_ => 0, _ => 0,
} }
} }
@ -168,7 +172,11 @@ impl<'src> Token<'src> {
} }
pub fn is_right_associative(&self) -> bool { pub fn is_right_associative(&self) -> bool {
matches!(self, Token::Semicolon) matches!(self, Token::Equal | Token::PlusEqual)
}
pub fn is_prefix(&self) -> bool {
matches!(self, Token::Bang | Token::Minus)
} }
pub fn is_postfix(&self) -> bool { pub fn is_postfix(&self) -> bool {
@ -185,6 +193,7 @@ impl<'src> Display for Token<'src> {
impl<'src> PartialEq for Token<'src> { impl<'src> PartialEq for Token<'src> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (self, other) { match (self, other) {
(Token::Bang, Token::Bang) => true,
(Token::Boolean(left), Token::Boolean(right)) => left == right, (Token::Boolean(left), Token::Boolean(right)) => left == right,
(Token::Comma, Token::Comma) => true, (Token::Comma, Token::Comma) => true,
(Token::Dot, Token::Dot) => true, (Token::Dot, Token::Dot) => true,
@ -254,6 +263,7 @@ pub enum TokenOwned {
WriteLine, WriteLine,
// Symbols // Symbols
Bang,
Comma, Comma,
Dot, Dot,
DoubleAmpersand, DoubleAmpersand,
@ -282,6 +292,7 @@ pub enum TokenOwned {
impl Display for TokenOwned { impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), TokenOwned::Boolean(boolean) => write!(f, "{boolean}"),
TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f),
TokenOwned::Dot => Token::Dot.fmt(f), TokenOwned::Dot => Token::Dot.fmt(f),

View File

@ -105,6 +105,14 @@ impl Value {
} }
} }
pub fn as_float(&self) -> Option<f64> {
if let ValueInner::Float(float) = self.0.as_ref() {
Some(*float)
} else {
None
}
}
pub fn as_function(&self) -> Option<&Function> { pub fn as_function(&self) -> Option<&Function> {
if let ValueInner::Function(function) = self.0.as_ref() { if let ValueInner::Function(function) = self.0.as_ref() {
Some(function) Some(function)

View File

@ -6,8 +6,8 @@ use std::{
use crate::{ use crate::{
abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer, abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer,
BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement, Value, BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement,
ValueError, UnaryOperator, Value, ValueError,
}; };
pub fn run(source: &str) -> Result<Option<Value>, DustError> { pub fn run(source: &str) -> Result<Option<Value>, DustError> {
@ -500,6 +500,33 @@ impl Vm {
position: right_span, position: right_span,
}) })
} }
Statement::UnaryOperation { operator, operand } => {
let position = operand.position;
let value = if let Some(value) = self.run_statement(*operand, context)? {
value
} else {
return Err(VmError::ExpectedValue { position });
};
match operator.inner {
UnaryOperator::Negate => {
if let Some(value) = value.as_integer() {
Ok(Some(Value::integer(-value)))
} else if let Some(value) = value.as_float() {
Ok(Some(Value::float(-value)))
} else {
Err(VmError::ExpectedNumber { position })
}
}
UnaryOperator::Not => {
if let Some(value) = value.as_boolean() {
Ok(Some(Value::boolean(!value)))
} else {
Err(VmError::ExpectedBoolean { position })
}
}
}
}
Statement::While { condition, body } => { Statement::While { condition, body } => {
let mut return_value = None; let mut return_value = None;
@ -555,6 +582,9 @@ pub enum VmError {
ExpectedInteger { ExpectedInteger {
position: Span, position: Span,
}, },
ExpectedNumber {
position: Span,
},
ExpectedFunction { ExpectedFunction {
actual: Value, actual: Value,
position: Span, position: Span,
@ -588,6 +618,7 @@ impl VmError {
Self::ExpectedInteger { position } => *position, Self::ExpectedInteger { position } => *position,
Self::ExpectedFunction { position, .. } => *position, Self::ExpectedFunction { position, .. } => *position,
Self::ExpectedList { position } => *position, Self::ExpectedList { position } => *position,
Self::ExpectedNumber { position } => *position,
Self::ExpectedValue { position } => *position, Self::ExpectedValue { position } => *position,
Self::UndefinedVariable { identifier } => identifier.position, Self::UndefinedVariable { identifier } => identifier.position,
Self::UndefinedProperty { Self::UndefinedProperty {
@ -637,6 +668,13 @@ impl Display for VmError {
Self::ExpectedList { position } => { Self::ExpectedList { position } => {
write!(f, "Expected a list at position: {:?}", position) write!(f, "Expected a list at position: {:?}", position)
} }
Self::ExpectedNumber { position } => {
write!(
f,
"Expected an integer or float at position: {:?}",
position
)
}
Self::ExpectedValue { position } => { Self::ExpectedValue { position } => {
write!(f, "Expected a value at position: {:?}", position) write!(f, "Expected a value at position: {:?}", position)
} }
@ -656,6 +694,20 @@ impl Display for VmError {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn negate_expression() {
let input = "x = -42; -x";
assert_eq!(run(input), Ok(Some(Value::integer(42))));
}
#[test]
fn not_expression() {
let input = "!(1 == 2 || 3 == 4 || 5 == 6)";
assert_eq!(run(input), Ok(Some(Value::boolean(true))));
}
#[test] #[test]
fn list_index() { fn list_index() {
let input = "[1, 42, 3].1"; let input = "[1, 42, 3].1";