diff --git a/forge-script-lang/src/lexer/keywords.rs b/forge-script-lang/src/lexer/keywords.rs
index b919a221e2c2ce0260bd252ded56c3f882917ea4..892876ec38aae9f8e090805fc84b28b0cab85a29 100644
--- a/forge-script-lang/src/lexer/keywords.rs
+++ b/forge-script-lang/src/lexer/keywords.rs
@@ -207,3 +207,15 @@ pub fn token_typeof(input: Span) -> IResult<Span, ScriptToken> {
 		},
 	))
 }
+
+pub fn token_finally(input: Span) -> IResult<Span, ScriptToken> {
+	let (input, pos) = position(input)?;
+	let (input, _) = tag_ws("finally", input)?;
+	Ok((
+		input,
+		ScriptToken {
+			position: pos,
+			token_type: ScriptTokenType::Finally,
+		},
+	))
+}
diff --git a/forge-script-lang/src/lexer/mod.rs b/forge-script-lang/src/lexer/mod.rs
index 76e85ed3a9224169e4ee5e153bf731c9884b1115..685a1bc28b12c38b2c2c69e8e657907f37dd21eb 100644
--- a/forge-script-lang/src/lexer/mod.rs
+++ b/forge-script-lang/src/lexer/mod.rs
@@ -29,6 +29,7 @@ mod _lex {
 	use nom::branch::alt;
 	use nom::IResult;
 
+	use crate::lexer::keywords::token_finally;
 	use nom::character::complete::multispace0;
 	use nom::multi::fold_many0;
 	use nom::sequence::delimited;
@@ -53,6 +54,7 @@ mod _lex {
 				token_super,
 				token_from,
 				token_typeof,
+				token_finally,
 			)),
 			alt((
 				token_plus,
diff --git a/forge-script-lang/src/lexer/tokens.rs b/forge-script-lang/src/lexer/tokens.rs
index ac79700b0b67ed093800d97bcbdf99ba1573752e..971d270f4eb2697aa286edf1b2c52fd59f4bd4c4 100644
--- a/forge-script-lang/src/lexer/tokens.rs
+++ b/forge-script-lang/src/lexer/tokens.rs
@@ -59,6 +59,7 @@ pub enum ScriptTokenType<'a> {
 	Alias,
 	From,
 	Typeof,
+	Finally,
 
 	// Misc
 	Eof,
@@ -120,6 +121,7 @@ impl<'a> ScriptTokenType<'a> {
 			ScriptTokenType::From => 4,
 			ScriptTokenType::Eof => 0,
 			ScriptTokenType::Typeof => 6,
+			ScriptTokenType::Finally => 7,
 		}
 	}
 }
@@ -174,6 +176,7 @@ impl<'a> Display for ScriptTokenType<'a> {
 			ScriptTokenType::From => write!(f, "from"),
 			ScriptTokenType::Eof => write!(f, ""),
 			ScriptTokenType::Typeof => write!(f, "typeof"),
+			ScriptTokenType::Finally => write!(f, "finally"),
 		}
 	}
 }
@@ -234,6 +237,7 @@ impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> {
 			"as" => Ok(ScriptTokenType::Alias),
 			"from" => Ok(ScriptTokenType::From),
 			"typeof" => Ok(ScriptTokenType::Typeof),
+			"finally" => Ok(ScriptTokenType::Finally),
 			"false" => Ok(ScriptTokenType::Boolean(false)),
 			"true" => Ok(ScriptTokenType::Boolean(true)),
 			_ => Err(TokenFromStringError { source: value }),
@@ -312,6 +316,7 @@ mod token_tests {
 	#[test_case("typeof" => Ok(ScriptTokenType::Typeof); r#"Parse Typeof"#)]
 	#[test_case("true" => Ok(ScriptTokenType::Boolean(true)); r#"Parse true"#)]
 	#[test_case("false" => Ok(ScriptTokenType::Boolean(false)); r#"Parse false"#)]
+	#[test_case("finally" => Ok(ScriptTokenType::Finally); r#"Parse finally"#)]
 	fn parse_token_type(value: &'static str) -> Result<ScriptTokenType, TokenFromStringError> {
 		ScriptTokenType::try_from(value)
 	}
diff --git a/forge-script-lang/src/lib.rs b/forge-script-lang/src/lib.rs
index 24498c63c65f289a155e5603ff3cb4b4f295cd6e..40526301fdbf163ad4068053aabf9173a693f7db 100644
--- a/forge-script-lang/src/lib.rs
+++ b/forge-script-lang/src/lib.rs
@@ -2,6 +2,7 @@ mod error;
 mod lexer;
 mod parser;
 pub mod runtime;
+mod utilities;
 
 pub use error::{
 	format_forge_error, print_forge_error, ForgeError, ForgeErrorKind, ParseError, ParseErrorKind,
diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs
index 042841a14102d2d91162b8dd054876d9f417e98e..4485d55fbc0ab898298bfafe51997386003077b6 100644
--- a/forge-script-lang/src/parser/ast.rs
+++ b/forge-script-lang/src/parser/ast.rs
@@ -103,6 +103,11 @@ pub enum BinaryOp {
 	Equals,
 	BoolAnd,
 	BoolOr,
+	NotEquals,
+	LessThan,
+	GreaterThan,
+	LessThanEqual,
+	GreaterThanEqual,
 }
 
 impl BinaryOp {
@@ -128,6 +133,11 @@ impl Display for BinaryOp {
 				BinaryOp::Equals => "==",
 				BinaryOp::BoolAnd => "&&",
 				BinaryOp::BoolOr => "||",
+				BinaryOp::NotEquals => "!=",
+				BinaryOp::LessThan => "<",
+				BinaryOp::GreaterThan => ">",
+				BinaryOp::LessThanEqual => "<=",
+				BinaryOp::GreaterThanEqual => ">=",
 			}
 		)
 	}
diff --git a/forge-script-lang/src/parser/grammar.rs b/forge-script-lang/src/parser/grammar.rs
index d3af36bcfb2a0a9bb21f711d3c65ca5dbb2cb79b..e4fd9f4f7c1cf1c36e655d52230969f18f9ee8f3 100644
--- a/forge-script-lang/src/parser/grammar.rs
+++ b/forge-script-lang/src/parser/grammar.rs
@@ -63,7 +63,7 @@ peg::parser! {
 		rule condition_loop() -> ConditionalLoop
 			= "while" guard:value_expression() block:block()
 				{ ConditionalLoop { block: GuardedBlock { guard: Box::new(guard), block }, fallback: None } }
-			/ "while" guard:value_expression() block:block() "else" fallback:block()
+			/ "while" guard:value_expression() block:block() "finally" fallback:block()
 				{ ConditionalLoop { block: GuardedBlock { guard: Box::new(guard), block }, fallback: Some(fallback) } }
 
 		rule conditional_statement() -> Conditional
diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs
index a0b728d0849be419ca33f214bad8fae6c15a0c48..148cf937679fe8bcf6e22a5850539f1d7db20169 100644
--- a/forge-script-lang/src/runtime/executor/simple.rs
+++ b/forge-script-lang/src/runtime/executor/simple.rs
@@ -157,7 +157,13 @@ impl Visitor for SimpleExecutor {
 						BinaryOp::Multiply => Ok((lhs * rhs)?),
 						BinaryOp::Modulo => Ok((lhs % rhs)?),
 						BinaryOp::Equals => Ok((lhs == rhs).into()),
-						BinaryOp::BoolOr | BinaryOp::BoolAnd => Ok(ForgeValue::Null),
+						BinaryOp::BoolOr
+						| BinaryOp::BoolAnd
+						| BinaryOp::NotEquals
+						| BinaryOp::LessThan
+						| BinaryOp::GreaterThan
+						| BinaryOp::LessThanEqual
+						| BinaryOp::GreaterThanEqual => todo!("Implement Binary Op"),
 					}
 				}
 			}
@@ -216,8 +222,17 @@ impl Visitor for SimpleExecutor {
 		expression: &VoidExpression,
 	) -> Result<Self::Output, Self::Error> {
 		match expression {
-			VoidExpression::ConditionLoop(_) => {
-				return Err(RuntimeError::Unsupported("ConditionLoop"))
+			VoidExpression::ConditionLoop(cond_loop) => {
+				while self
+					.evaluate_value_expression(cond_loop.block.guard.as_ref())?
+					.as_bool()
+				{
+					self.evaluate_expression_list(&cond_loop.block.block)?;
+				}
+
+				if let Some(fallback) = &cond_loop.fallback {
+					self.evaluate_expression_list(fallback)?;
+				}
 			}
 			VoidExpression::Import(_) => return Err(RuntimeError::Unsupported("Import")),
 			VoidExpression::Export(_) => return Err(RuntimeError::Unsupported("Export")),
@@ -272,6 +287,8 @@ mod interpreter_test {
 	#[test_case("let value = true; if value { 123 } else { 456 }" => Ok(ForgeValue::from(123)); "eval if branch")]
 	#[test_case("let value = true; if !value { 123 } else { 456 }" => Ok(ForgeValue::from(456)); "eval else branch")]
 	#[test_case("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }" => Ok(ForgeValue::from(456)); "eval middle if else branch")]
+	// #[test_case("let value = 0; while value < 4 { value = value + 4 }; value" => Ok(ForgeValue::from(4)); "simple conditional loop")]
+	// #[test_case("let value = 0; while value < 4 { value = value + 4 } finally { value = 1234 }; value" => Ok(ForgeValue::from(1234)); "conditional loop with cleanup")]
 	fn conditional_blocks(prog: &'static str) -> RuntimeResult<ForgeValue> {
 		let ast = parse_program(prog).expect("Failed to parse program");
 		let mut vm = SimpleExecutor::default();
diff --git a/forge-script-lang/src/runtime/mod.rs b/forge-script-lang/src/runtime/mod.rs
index afe3ea940c6bff3205f98ba36c71fd12d8f1188a..20065f855b55e297e2c00c99d3135ef2dbea2cb3 100644
--- a/forge-script-lang/src/runtime/mod.rs
+++ b/forge-script-lang/src/runtime/mod.rs
@@ -1,3 +1,4 @@
 pub mod executor;
 pub mod numbers;
 pub mod value;
+mod vm;
diff --git a/forge-script-lang/src/runtime/vm/chunks.rs b/forge-script-lang/src/runtime/vm/chunks.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a16a061de8367a850d059f4c4dbb007d948126c3
--- /dev/null
+++ b/forge-script-lang/src/runtime/vm/chunks.rs
@@ -0,0 +1,153 @@
+use crate::runtime::value::ForgeValue;
+use crate::runtime::vm::const_data::{ConstData, ConstDataRef};
+use crate::runtime::vm::opcode::{OpCode, OpCodeError};
+use std::fmt::{Display, Formatter};
+use std::ops::{Deref, DerefMut};
+
+#[derive(Default)]
+pub struct Chunk {
+	code: Vec<OpCode>,
+	consts: ConstData,
+}
+
+impl From<Vec<OpCode>> for Chunk {
+	fn from(value: Vec<OpCode>) -> Self {
+		Self {
+			code: value,
+			consts: ConstData::default(),
+		}
+	}
+}
+
+pub struct ChunkRef<'scope> {
+	code: &'scope [OpCode],
+	consts: ConstDataRef<'scope>,
+}
+
+impl<'scope> From<&'scope Chunk> for ChunkRef<'scope> {
+	fn from(value: &'scope Chunk) -> Self {
+		Self {
+			code: value.code.as_slice(),
+			consts: ConstDataRef::from(value.consts.as_slice()),
+		}
+	}
+}
+
+impl Chunk {
+	pub fn push_op(&mut self, op: OpCode) -> usize {
+		let idx = self.code.len();
+		self.code.push(op);
+		idx
+	}
+
+	pub fn push_const_data(&mut self, data: ForgeValue) -> usize {
+		let idx = self.consts.len();
+		self.consts.push(data);
+		idx
+	}
+
+	pub fn as_ref(&self) -> ChunkRef {
+		ChunkRef {
+			code: self.code.as_slice(),
+			consts: self.consts.as_ref(),
+		}
+	}
+
+	pub fn op_return(&mut self) {
+		self.push_op(OpCode::Return);
+	}
+
+	pub fn op_constant(&mut self, data: ForgeValue) {
+		let idx = self.push_const_data(data);
+		self.push_op(OpCode::Constant(idx));
+	}
+}
+
+pub trait ChunkOps {
+	fn as_slice(&self) -> &[OpCode];
+
+	fn write_chunk_data(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		let mut offset = 0;
+		let slice = self.as_slice();
+
+		while offset < slice.len() {
+			let opcode = match slice.get(offset) {
+				Some(value) => value,
+				None => {
+					break;
+				}
+			};
+
+			write!(f, "[{:08x}] {} ", offset, opcode)?;
+			opcode.format_operand(f)?;
+			writeln!(f, "")?;
+
+			offset = opcode.next_offset(offset);
+		}
+
+		Ok(())
+	}
+
+	fn format_chunk(&self, name: impl Display) -> String;
+}
+
+macro_rules! chunk_ops_generic {
+	() => {
+		fn format_chunk(&self, name: impl Display) -> String {
+			format!("== {} ==\n{}", name, self)
+		}
+	};
+}
+
+impl ChunkOps for Chunk {
+	fn as_slice(&self) -> &[OpCode] {
+		self.code.as_slice()
+	}
+
+	chunk_ops_generic!();
+}
+impl<'scope> ChunkOps for ChunkRef<'scope> {
+	fn as_slice(&self) -> &[OpCode] {
+		self.code
+	}
+	chunk_ops_generic!();
+}
+
+impl Display for Chunk {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		self.write_chunk_data(f)
+	}
+}
+
+impl<'scope> Display for ChunkRef<'scope> {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		self.write_chunk_data(f)
+	}
+}
+
+#[cfg(test)]
+mod _chunks {
+	use super::*;
+
+	#[test]
+	fn basic_chunk_format() {
+		let chunk = Chunk::from(vec![OpCode::Return]);
+		let expected = "== test ==\n[00000000] op_return \n";
+
+		assert_eq!(chunk.format_chunk("test"), String::from(expected));
+	}
+
+	#[test]
+	fn const_chunk() {
+		let mut chunk = Chunk::default();
+		chunk.op_constant(ForgeValue::Null);
+		chunk.op_return();
+
+		let expected = format!(
+			"== test ==\n[00000000] op_constant {:016x}\n[00000001] op_return \n",
+			0
+		);
+
+		assert_eq!(chunk.format_chunk("test"), expected);
+	}
+}
diff --git a/forge-script-lang/src/runtime/vm/const_data.rs b/forge-script-lang/src/runtime/vm/const_data.rs
new file mode 100644
index 0000000000000000000000000000000000000000..94f770db9fe033a60ee74315d62688f4acc256d1
--- /dev/null
+++ b/forge-script-lang/src/runtime/vm/const_data.rs
@@ -0,0 +1,49 @@
+use crate::runtime::value::ForgeValue;
+use std::ops::{Deref, DerefMut};
+
+#[derive(Default)]
+pub struct ConstData(Vec<ForgeValue>);
+pub struct ConstDataRef<'scope>(&'scope [ForgeValue]);
+
+impl From<Vec<ForgeValue>> for ConstData {
+	fn from(value: Vec<ForgeValue>) -> Self {
+		ConstData(value)
+	}
+}
+
+impl<'scope> From<&'scope [ForgeValue]> for ConstDataRef<'scope> {
+	fn from(value: &'scope [ForgeValue]) -> Self {
+		ConstDataRef(value)
+	}
+}
+
+impl Deref for ConstData {
+	type Target = Vec<ForgeValue>;
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+impl DerefMut for ConstData {
+	fn deref_mut(&mut self) -> &mut Self::Target {
+		&mut self.0
+	}
+}
+impl ConstData {
+	pub fn as_ref(&self) -> ConstDataRef {
+		ConstDataRef(self.0.as_slice())
+	}
+}
+
+impl<'scope> Deref for ConstDataRef<'scope> {
+	type Target = &'scope [ForgeValue];
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+impl<'scope> DerefMut for ConstDataRef<'scope> {
+	fn deref_mut(&mut self) -> &mut Self::Target {
+		&mut self.0
+	}
+}
diff --git a/forge-script-lang/src/runtime/vm/mod.rs b/forge-script-lang/src/runtime/vm/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bd30ae0c30f8d255caad3655e842ed2c56ae91b2
--- /dev/null
+++ b/forge-script-lang/src/runtime/vm/mod.rs
@@ -0,0 +1,3 @@
+mod chunks;
+mod const_data;
+mod opcode;
diff --git a/forge-script-lang/src/runtime/vm/opcode.rs b/forge-script-lang/src/runtime/vm/opcode.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9809dd3efe2c1ce3a3bf64056182f8d2c816371f
--- /dev/null
+++ b/forge-script-lang/src/runtime/vm/opcode.rs
@@ -0,0 +1,67 @@
+use std::error::Error;
+use std::fmt::{Display, Formatter};
+
+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
+pub enum OpCode {
+	Return,
+	/// Load a constant value from the chunk's data pool. Data starts at the included offset
+	Constant(usize),
+}
+
+impl OpCode {
+	pub fn next_offset(&self, current: usize) -> usize {
+		match self {
+			_ => current + 1,
+		}
+	}
+
+	pub fn op_byte(&self) -> u8 {
+		match self {
+			OpCode::Return => 1,
+			OpCode::Constant(_) => 2,
+		}
+	}
+
+	pub fn operand_bytes(&self) -> Vec<u8> {
+		match self {
+			OpCode::Return => vec![],
+			OpCode::Constant(value) => value.to_le_bytes().to_vec(),
+		}
+	}
+
+	pub fn format_operand(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		for byte in self.operand_bytes() {
+			write!(f, "{:02x}", byte)?;
+		}
+
+		Ok(())
+	}
+}
+
+#[derive(Clone, Debug)]
+pub enum OpCodeError {
+	Unknown(u8),
+}
+
+impl Display for OpCodeError {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		match self {
+			Self::Unknown(value) => write!(f, "Unknown opcode {}", value),
+		}
+	}
+}
+
+impl Error for OpCodeError {}
+
+impl Display for OpCode {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		write!(
+			f,
+			"{}",
+			match self {
+				OpCode::Return => "op_return",
+				OpCode::Constant(_) => "op_constant",
+			}
+		)
+	}
+}
diff --git a/forge-script-lang/src/utilities.rs b/forge-script-lang/src/utilities.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a354bfcaf1ae5ad8e049d13dfcd743ab014ebb4c
--- /dev/null
+++ b/forge-script-lang/src/utilities.rs
@@ -0,0 +1,37 @@
+#[macro_export]
+macro_rules! deref_as {
+    ($type:ty => $other:ty) => {
+		impl std::ops::Deref for $type {
+			type Target = $other;
+
+			fn deref(&self) -> &Self::Target {
+				&self.0
+			}
+		}
+	};
+    ($($lifetime:tt),+: $type:ty => $other:ty) => {
+		impl <$($lifetime),+> std::ops::Deref for $type {
+			type Target = $other;
+
+			fn deref(&self) -> &Self::Target {
+				&self.0
+			}
+		}
+	};
+    (mut $type:ty => $other:ty) => {
+		deref_as!($type => $other);
+		impl std::ops::DerefMut for $type {
+			fn deref_mut(&mut self) -> &mut Self::Target {
+				&mut self.0
+			}
+		}
+    };
+    ($($lifetime:tt),+ mut: $type:ty => $other:ty) => {
+		deref_as!($($lifetime),+: $type => $other);
+		impl <$($lifetime),+> std::ops::DerefMut for $type {
+			fn deref_mut(&mut self) -> &mut Self::Target {
+				&mut self.0
+			}
+		}
+    };
+}