Begin adding unary operators
This commit is contained in:
parent
74cfef1832
commit
2c374a1cd7
@ -37,13 +37,19 @@ pub enum Statement {
|
||||
// A sequence of statements
|
||||
Block(Vec<Node<Statement>>),
|
||||
|
||||
// Logic, math and comparison expressions
|
||||
// Assignment, logic, math and comparison expressions with two operands
|
||||
BinaryOperation {
|
||||
left: Box<Node<Statement>>,
|
||||
operator: Node<BinaryOperator>,
|
||||
right: Box<Node<Statement>>,
|
||||
},
|
||||
|
||||
// Logic and math expressions with one operand
|
||||
UnaryOperation {
|
||||
operator: Node<UnaryOperator>,
|
||||
operand: Box<Node<Statement>>,
|
||||
},
|
||||
|
||||
// Function calls
|
||||
BuiltInFunctionCall {
|
||||
function: BuiltInFunction,
|
||||
@ -152,6 +158,10 @@ impl Statement {
|
||||
}
|
||||
Statement::Nil(_) => 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,
|
||||
}
|
||||
}
|
||||
@ -331,6 +341,9 @@ impl Display for Statement {
|
||||
}
|
||||
Statement::Nil(node) => write!(f, "{node};"),
|
||||
Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"),
|
||||
Statement::UnaryOperation { operator, operand } => {
|
||||
write!(f, "{operator}{operand}")
|
||||
}
|
||||
Statement::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, "!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, Context, DustError, Node, Span,
|
||||
Statement, Type,
|
||||
abstract_tree::{BinaryOperator, UnaryOperator},
|
||||
parse, AbstractSyntaxTree, Context, DustError, Node, Span, Statement, Type,
|
||||
};
|
||||
|
||||
/// 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 } => {
|
||||
self.analyze_statement(condition)?;
|
||||
self.analyze_statement(body)?;
|
||||
|
@ -133,7 +133,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn context_removes_used_variables() {
|
||||
fn context_removes_variables() {
|
||||
env_logger::builder().is_test(true).try_init().unwrap();
|
||||
|
||||
let source = "
|
||||
@ -150,7 +150,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_removes_variables_after_loop() {
|
||||
fn garbage_collector_does_not_break_loops() {
|
||||
let source = "
|
||||
y = 1
|
||||
z = 0
|
||||
|
@ -253,6 +253,11 @@ impl Lexer {
|
||||
});
|
||||
}
|
||||
}
|
||||
'!' => {
|
||||
self.position += 1;
|
||||
|
||||
(Token::Bang, (self.position - 1, self.position))
|
||||
}
|
||||
_ => {
|
||||
self.position += 1;
|
||||
|
||||
@ -479,6 +484,41 @@ impl Display for LexError {
|
||||
mod tests {
|
||||
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]
|
||||
fn if_else() {
|
||||
let input = "if x < 10 { x + 1 } else { x }";
|
||||
|
@ -14,7 +14,7 @@ pub mod r#type;
|
||||
pub mod value;
|
||||
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 built_in_function::{BuiltInFunction, BuiltInFunctionError};
|
||||
pub use context::{Context, VariableData};
|
||||
|
@ -13,7 +13,7 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
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.
|
||||
@ -163,6 +163,34 @@ impl<'src> Parser<'src> {
|
||||
|
||||
fn parse_primary(&mut self) -> Result<Node<Statement>, ParseError> {
|
||||
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) => {
|
||||
self.next_token()?;
|
||||
|
||||
@ -799,10 +827,83 @@ impl Display for ParseError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{abstract_tree::BinaryOperator, Identifier};
|
||||
use crate::{abstract_tree::BinaryOperator, Identifier, UnaryOperator};
|
||||
|
||||
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]
|
||||
fn r#if() {
|
||||
let input = "if x { y }";
|
||||
|
@ -28,6 +28,7 @@ pub enum Token<'src> {
|
||||
WriteLine,
|
||||
|
||||
// Symbols
|
||||
Bang,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
@ -56,6 +57,7 @@ pub enum Token<'src> {
|
||||
impl<'src> Token<'src> {
|
||||
pub fn to_owned(&self) -> TokenOwned {
|
||||
match self {
|
||||
Token::Bang => TokenOwned::Bang,
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
Token::Dot => TokenOwned::Dot,
|
||||
@ -104,6 +106,7 @@ impl<'src> Token<'src> {
|
||||
Token::Integer(integer_text) => integer_text,
|
||||
Token::String(text) => text,
|
||||
|
||||
Token::Bang => "!",
|
||||
Token::Comma => ",",
|
||||
Token::Dot => ".",
|
||||
Token::DoubleAmpersand => "&&",
|
||||
@ -147,18 +150,19 @@ impl<'src> Token<'src> {
|
||||
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Token::Dot => 12,
|
||||
Token::Star | Token::Slash | Token::Percent => 10,
|
||||
Token::Plus | Token::Minus => 9,
|
||||
Token::Dot => 9,
|
||||
Token::Star | Token::Slash | Token::Percent => 8,
|
||||
Token::Minus => 7,
|
||||
Token::Plus => 6,
|
||||
Token::DoubleEqual
|
||||
| Token::Less
|
||||
| Token::LessEqual
|
||||
| Token::Greater
|
||||
| Token::GreaterEqual => 8,
|
||||
Token::DoubleAmpersand => 7,
|
||||
Token::DoublePipe => 6,
|
||||
Token::Equal | Token::PlusEqual => 5,
|
||||
Token::Semicolon => 4,
|
||||
| Token::GreaterEqual => 5,
|
||||
Token::DoubleAmpersand => 4,
|
||||
Token::DoublePipe => 3,
|
||||
Token::Equal | Token::PlusEqual => 2,
|
||||
Token::Semicolon => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
@ -168,7 +172,11 @@ impl<'src> Token<'src> {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -185,6 +193,7 @@ impl<'src> Display for Token<'src> {
|
||||
impl<'src> PartialEq for Token<'src> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Token::Bang, Token::Bang) => true,
|
||||
(Token::Boolean(left), Token::Boolean(right)) => left == right,
|
||||
(Token::Comma, Token::Comma) => true,
|
||||
(Token::Dot, Token::Dot) => true,
|
||||
@ -254,6 +263,7 @@ pub enum TokenOwned {
|
||||
WriteLine,
|
||||
|
||||
// Symbols
|
||||
Bang,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
@ -282,6 +292,7 @@ pub enum TokenOwned {
|
||||
impl Display for TokenOwned {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenOwned::Bang => Token::Bang.fmt(f),
|
||||
TokenOwned::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
||||
|
@ -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> {
|
||||
if let ValueInner::Function(function) = self.0.as_ref() {
|
||||
Some(function)
|
||||
|
@ -6,8 +6,8 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer,
|
||||
BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement, Value,
|
||||
ValueError,
|
||||
BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement,
|
||||
UnaryOperator, Value, ValueError,
|
||||
};
|
||||
|
||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||
@ -500,6 +500,33 @@ impl Vm {
|
||||
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 } => {
|
||||
let mut return_value = None;
|
||||
|
||||
@ -555,6 +582,9 @@ pub enum VmError {
|
||||
ExpectedInteger {
|
||||
position: Span,
|
||||
},
|
||||
ExpectedNumber {
|
||||
position: Span,
|
||||
},
|
||||
ExpectedFunction {
|
||||
actual: Value,
|
||||
position: Span,
|
||||
@ -588,6 +618,7 @@ impl VmError {
|
||||
Self::ExpectedInteger { position } => *position,
|
||||
Self::ExpectedFunction { position, .. } => *position,
|
||||
Self::ExpectedList { position } => *position,
|
||||
Self::ExpectedNumber { position } => *position,
|
||||
Self::ExpectedValue { position } => *position,
|
||||
Self::UndefinedVariable { identifier } => identifier.position,
|
||||
Self::UndefinedProperty {
|
||||
@ -637,6 +668,13 @@ impl Display for VmError {
|
||||
Self::ExpectedList { 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 } => {
|
||||
write!(f, "Expected a value at position: {:?}", position)
|
||||
}
|
||||
@ -656,6 +694,20 @@ impl Display for VmError {
|
||||
mod tests {
|
||||
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]
|
||||
fn list_index() {
|
||||
let input = "[1, 42, 3].1";
|
||||
|
Loading…
Reference in New Issue
Block a user