From 1e477907372f1b600b5fa6b35aa0564837764ce4 Mon Sep 17 00:00:00 2001
From: Zak Farmer <zak@3sidedcube.com>
Date: Tue, 24 Oct 2023 18:10:00 +0100
Subject: [PATCH] feat: bang and minus prefix operators

---
 compiler/src/lib.rs              | 11 ++++
 compiler/tests/compiler_tests.rs | 17 ++++++
 vm/src/lib.rs                    | 31 +++++++++++
 vm/tests/vm_tests.rs             | 92 ++++++++++++++++++++++++++++++++
 4 files changed, 151 insertions(+)

diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs
index 5bd8696..28f687e 100644
--- a/compiler/src/lib.rs
+++ b/compiler/src/lib.rs
@@ -139,6 +139,17 @@ impl Compiler {
 
                 Ok(())
             }
+            Expression::Prefix(prefix) => {
+                self.compile_expression(&prefix.right)?;
+
+                match prefix.operator.as_str() {
+                    "!" => self.emit(opcode::Opcode::OpBang, vec![]),
+                    "-" => self.emit(opcode::Opcode::OpMinus, vec![]),
+                    _ => return Err(Error::msg("compile_expression: unimplemented")),
+                };
+
+                Ok(())
+            }
             Expression::Literal(literal) => match literal {
                 Literal::Boolean(boolean) => match boolean {
                     BooleanLiteral { value: true, .. } => {
diff --git a/compiler/tests/compiler_tests.rs b/compiler/tests/compiler_tests.rs
index b64edf2..764c622 100644
--- a/compiler/tests/compiler_tests.rs
+++ b/compiler/tests/compiler_tests.rs
@@ -80,6 +80,14 @@ fn test_boolean_expressions() -> Result<(), Error> {
                 opcode::make(opcode::Opcode::OpNotEqual, &vec![]),
             ],
         },
+        CompilerTestCase {
+            input: "!true".to_string(),
+            expected_constants: vec![],
+            expected_instructions: vec![
+                opcode::make(opcode::Opcode::OpTrue, &vec![]),
+                opcode::make(opcode::Opcode::OpBang, &vec![]),
+            ],
+        },
     ];
 
     run_compiler_tests(tests)?;
@@ -130,6 +138,15 @@ fn test_integer_arithmetic() -> Result<(), Error> {
                 opcode::make(opcode::Opcode::OpPop, &vec![0])
             ]
         },
+        CompilerTestCase {
+            input: "-1".to_string(),
+            expected_constants: vec![Object::Integer(1)],
+            expected_instructions: vec![
+                opcode::make(opcode::Opcode::OpConst, &vec![0]),
+                opcode::make(opcode::Opcode::OpMinus, &vec![]),
+                opcode::make(opcode::Opcode::OpPop, &vec![0])
+            ]
+        },
     ];
 
     run_compiler_tests(tests)?;
diff --git a/vm/src/lib.rs b/vm/src/lib.rs
index 97288ce..7f50f50 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -171,6 +171,37 @@ impl Vm {
                     self.stack_pointer -= 1;
                     self.stack[self.stack_pointer - 1] = Rc::new(result);
                 }
+                Opcode::OpBang => {
+                    let operand = self.pop();
+
+                    let result = match &*operand {
+                        Object::Boolean(boolean) => Object::Boolean(!boolean),
+                        Object::Integer(integer) => Object::Boolean(!integer == 0),
+                        _ => {
+                            return Err(Error::msg(format!(
+                                "unsupported type for negation: !{}",
+                                operand
+                            )));
+                        }
+                    };
+
+                    self.push(Rc::new(result));
+                }
+                Opcode::OpMinus => {
+                    let operand = self.pop();
+
+                    let result = match &*operand {
+                        Object::Integer(integer) => Object::Integer(-integer),
+                        _ => {
+                            return Err(Error::msg(format!(
+                                "unsupported type for negation: -{}",
+                                operand
+                            )));
+                        }
+                    };
+
+                    self.push(Rc::new(result));
+                }
                 _ => {
                     return Err(Error::msg(format!("unknown opcode: {}", op)));
                 }
diff --git a/vm/tests/vm_tests.rs b/vm/tests/vm_tests.rs
index efc6458..9ccdece 100644
--- a/vm/tests/vm_tests.rs
+++ b/vm/tests/vm_tests.rs
@@ -41,6 +41,82 @@ fn test_boolean_expressions() -> Result<(), Error> {
             input: "false".to_string(),
             expected: "false".to_string(),
         },
+        VmTestCase {
+            input: "1 < 2".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "1 > 2".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "1 < 1".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "1 > 1".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "1 == 1".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "1 != 1".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "1 == 2".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "1 != 2".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "true == true".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "false == false".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "true == false".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "true != false".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "false != true".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "!true".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "!false".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "!5".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "!!true".to_string(),
+            expected: "true".to_string(),
+        },
+        VmTestCase {
+            input: "!!false".to_string(),
+            expected: "false".to_string(),
+        },
+        VmTestCase {
+            input: "!!5".to_string(),
+            expected: "true".to_string(),
+        },
     ];
 
     run_vm_tests(tests)?;
@@ -79,6 +155,22 @@ fn test_integer_arithmetic() -> Result<(), Error> {
             input: "50 / 2 * 2 + 10 - 5".to_string(),
             expected: "55".to_string(),
         },
+        VmTestCase {
+            input: "-5".to_string(),
+            expected: "-5".to_string(),
+        },
+        VmTestCase {
+            input: "-10".to_string(),
+            expected: "-10".to_string(),
+        },
+        VmTestCase {
+            input: "-50 + 100 + -50".to_string(),
+            expected: "0".to_string(),
+        },
+        VmTestCase {
+            input: "(5 + 10 * 2 + 15 / 3) * 2 + -10".to_string(),
+            expected: "50".to_string(),
+        },
     ];
 
     run_vm_tests(tests)?;