Begin adding unary operators
This commit is contained in:
parent
74cfef1832
commit
2c374a1cd7
@ -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, "!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)?;
|
||||||
|
@ -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
|
||||||
|
@ -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 }";
|
||||||
|
@ -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};
|
||||||
|
@ -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 }";
|
||||||
|
@ -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),
|
||||||
|
@ -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)
|
||||||
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user