diff --git a/.idea/micro_script.iml b/.idea/micro_script.iml
index 5fd23c3ce5fd9252b9878424de4f9e0b64a3dbb0..031f739285ba93e27be92365ab784da41789b200 100644
--- a/.idea/micro_script.iml
+++ b/.idea/micro_script.iml
@@ -7,6 +7,7 @@
       <sourceFolder url="file://$MODULE_DIR$/forge-script-web/src" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/forge-script/src" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/forge-script-lang/tests" isTestSource="true" />
       <excludeFolder url="file://$MODULE_DIR$/target" />
     </content>
     <orderEntry type="inheritedJdk" />
diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs
index e61e2405480a6c6247281d0d6efb141a52d3c3b4..7bc290f786436aeccf0bf9c2fc9c47dfc127cbf6 100644
--- a/forge-script-lang/src/parser/ast.rs
+++ b/forge-script-lang/src/parser/ast.rs
@@ -7,13 +7,21 @@ pub trait AstNode {}
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Program(pub ExpressionList);
 impl AstNode for Program {}
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct ExpressionList {
 	pub expressions: Vec<Expression>,
 	pub is_void: bool,
@@ -155,11 +163,16 @@ pub enum VoidExpression {
 	Import(Import),
 	Export(Export),
 	Print(Print),
+	Return(Return),
 }
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct GroupedExpression {
 	pub inner: Box<ValueExpression>,
 }
@@ -297,12 +310,20 @@ impl ValueExpression {
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct TypeofValue(pub Box<ValueExpression>);
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct ConditionalLoop {
 	pub block: GuardedBlock,
 	pub fallback: Option<ExpressionList>,
@@ -325,7 +346,11 @@ impl ConditionalLoop {
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Conditional {
 	pub blocks: Vec<GuardedBlock>,
 	pub fallback: Option<ExpressionList>,
@@ -333,7 +358,11 @@ pub struct Conditional {
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct GuardedBlock {
 	pub guard: Box<ValueExpression>,
 	pub block: ExpressionList,
@@ -341,7 +370,11 @@ pub struct GuardedBlock {
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Import {
 	pub source: String,
 	pub items: IdentifierList,
@@ -349,14 +382,22 @@ pub struct Import {
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Export {
 	pub items: IdentifierList,
 }
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Print {
 	pub expr: Box<ValueExpression>,
 }
@@ -368,12 +409,34 @@ impl From<ValueExpression> for Print {
 	}
 }
 
+#[derive(Clone)]
+#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
+pub struct Return {
+	pub expr: Box<ValueExpression>,
+}
+impl From<ValueExpression> for Return {
+	fn from(value: ValueExpression) -> Self {
+		Return {
+			expr: Box::new(value),
+		}
+	}
+}
+
 pub type IdentifierList = Vec<IdentifierNode>;
 pub type ParameterList = Vec<ValueExpression>;
 
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct Identifier(pub String);
 impl Display for Identifier {
 	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -385,7 +448,11 @@ impl Display for Identifier {
 /// IdentifierAlias(original, alias) => identifier "as" alias
 #[derive(Clone)]
 #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+	feature = "serde",
+	derive(serde::Serialize, serde::Deserialize),
+	serde(rename_all = "snake_case")
+)]
 pub struct IdentifierAlias(pub String, pub String);
 impl Display for IdentifierAlias {
 	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -568,5 +635,6 @@ impl_into_void_expr!(
 	Print => VoidExpression::Print,
 	ConditionalLoop => VoidExpression::ConditionLoop,
 	Import => VoidExpression::Import,
-	Export => VoidExpression::Export
+	Export => VoidExpression::Export,
+	Return => VoidExpression::Return
 );
diff --git a/forge-script-lang/src/parser/forge_grammar.lalrpop b/forge-script-lang/src/parser/forge_grammar.lalrpop
index e81ee0870f67b46ae61b1a09a45e66b6c7b3a417..0d20fe9758741a8f785d38a5a383106d53ffd044 100644
--- a/forge-script-lang/src/parser/forge_grammar.lalrpop
+++ b/forge-script-lang/src/parser/forge_grammar.lalrpop
@@ -25,18 +25,22 @@ ExpressionList: ExpressionList = {
     },
 }
 
+FunctionDeclParamList = CommaSeperatedList<FunctionDeclParam>;
+
+FunctionDeclParam: DeclareIdent = {
+    Identifier => DeclareIdent::WithoutValue(<>),
+    <id:Identifier> "=" <def:ValueExpression> => DeclareIdent::WithValue(Assignment {
+        ident: id,
+        value: Box::new(def),
+    }),
+}
+
 ParameterList: Vec<ValueExpression> = {
     "(" <CommaSeperatedValueExpressionList> ")" => <>,
     "(" ")" => Vec::new(),
 }
 
-CommaSeperatedValueExpressionList: Vec<ValueExpression> = {
-    ValueExpression => vec![<>],
-    <mut ls:CommaSeperatedValueExpressionList> "," <ve:ValueExpression> => {
-        ls.push(ve);
-        ls
-    },
-}
+CommaSeperatedValueExpressionList = CommaSeperatedList<ValueExpression>;
 
 pub Expression: Expression = {
     ValueExpression => Expression::Value(<>),
@@ -44,9 +48,9 @@ pub Expression: Expression = {
 }
 
 ValueExpression: ValueExpression = {
-    BoolOrExpression => <>,
+    FirstClassDeclareExpression => <>,
     "let" <Identifier> => DeclareIdent::WithoutValue(<>).into(),
-    "let" <id:Identifier> "=" <expr:BoolOrExpression> => DeclareIdent::WithValue(
+    "let" <id:Identifier> "=" <expr:FirstClassDeclareExpression> => DeclareIdent::WithValue(
         Assignment { ident: id, value :Box::new(expr) }
     ).into(),
 }
@@ -55,6 +59,11 @@ VoidExpression: VoidExpression = {
     UnaryStatement => <>
 }
 
+FirstClassDeclareExpression: ValueExpression = {
+    BoolOrExpression => <>,
+    DeclareFunction => <>.into(),
+}
+
 BoolOrExpression: ValueExpression = {
     BoolAndExpression => <>,
     <lhs:BoolOrExpression> "||" <rhs:BoolAndExpression> => ValueExpression::bool_or(lhs, rhs),
@@ -93,7 +102,8 @@ MultiplicativeExpression: ValueExpression = {
 }
 
 UnaryStatement: VoidExpression = {
-    "print" <UnaryExpression> => Print { expr: Box::new(<>) }.into(),
+    "print" <FirstClassDeclareExpression> => Print { expr: Box::new(<>) }.into(),
+    "return" <FirstClassDeclareExpression> => Return { expr: Box::new(<>) }.into(),
 }
 
 UnaryExpression: ValueExpression = {
@@ -123,6 +133,14 @@ BaseExpression: ValueExpression = {
     "(" <ValueExpression> ")" => GroupedExpression { inner: Box::new(<>) }.into(),
 }
 
+DeclareFunction: DeclareFunction = {
+    "fn" <ident:Identifier> "(" <params:FunctionDeclParamList?> ")" <body:ExpressionBlock> => DeclareFunction {
+        ident,
+        params: params.unwrap_or_default(),
+        body,
+    }
+}
+
 AccessorChainAnyEnding: Vec<AccessorType> = {
     AccessorChainEndingWithProperty => <>,
     FunctionCall => vec![AccessorType::FunctionCall(<>)],
@@ -198,6 +216,14 @@ StringValue: String = {
     "owned_string" => <>,
 };
 
+CommaSeperatedList<Type>: Vec<Type> = {
+    Type => vec![<>],
+    <mut ls:CommaSeperatedList<Type>> "," <ty:Type> => {
+        ls.push(ty);
+        ls
+    }
+}
+
 extern {
     type Location = usize;
     type Error = String;
@@ -212,6 +238,7 @@ extern {
         "-" => ScriptTokenType::Minus,
         "+" => ScriptTokenType::Plus,
         ";" => ScriptTokenType::Semicolon,
+        ":" => ScriptTokenType::Colon,
         "/" => ScriptTokenType::Slash,
         "*" => ScriptTokenType::Asterisk,
         "!" => ScriptTokenType::Bang,
diff --git a/forge-script-lang/src/parser/forge_script.rs b/forge-script-lang/src/parser/forge_script.rs
index f1a6083847eeefd122206ee1043963956f4d3d41..6c2d5786ee0003f294d893394cc9359963701136 100644
--- a/forge-script-lang/src/parser/forge_script.rs
+++ b/forge-script-lang/src/parser/forge_script.rs
@@ -56,6 +56,10 @@ mod grammar_test {
 	#[test_case("foo.bar().baz" => matches Ok(_) ; "nested property accessor")]
 	#[test_case("if something_else {}.bar" => matches Ok(_) ; "expression property accessor")]
 	#[test_case("123 + if if foo { bar } else { baz } { 200 } else { 300 }" => matches Ok(_) ; "Parse inline condition")]
+	#[test_case("fn add(a, b) { a + b }" => matches Ok(_) ; "Declare basic fn")]
+	#[test_case("fn has_no_params() {}" => matches Ok(_) ; "Declare empty params fn")]
+	#[test_case("fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "Declare complex fn")]
+	#[test_case("let som_value = fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "First class fn")]
 	fn expression_parsing(prog: &str) -> Result<Expression, String> {
 		parse_expression(prog).map(|expr| {
 			dbg!(&expr);
diff --git a/forge-script-lang/src/runtime/executor/printer.rs b/forge-script-lang/src/runtime/executor/printer.rs
index 565b403d7bf1ccda69566628bd936784a6292b75..5ebad1361f87a3303cb2b1dab0bbcf69880522e5 100644
--- a/forge-script-lang/src/runtime/executor/printer.rs
+++ b/forge-script-lang/src/runtime/executor/printer.rs
@@ -268,6 +268,10 @@ impl Visitor for TreePrinter {
 				self.write("print ");
 				self.evaluate_value_expression(print.expr.as_ref());
 			}
+			VoidExpression::Return(ret) => {
+				self.write("return ");
+				self.evaluate_value_expression(ret.expr.as_ref());
+			}
 		}
 
 		Ok(())
diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs
index de0af3257b7c0954e6e9c2779b3342891547b4f7..ad063a8c11b8a710b4acd7369bd692b0c7c0815f 100644
--- a/forge-script-lang/src/runtime/executor/simple.rs
+++ b/forge-script-lang/src/runtime/executor/simple.rs
@@ -1,5 +1,5 @@
 use crate::parse::ast::{
-	BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, UnaryOp,
+	BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, Return, UnaryOp,
 	ValueExpression, VoidExpression,
 };
 use crate::parser::ast::Assignment;
@@ -241,6 +241,10 @@ impl Visitor for SimpleExecutor {
 				let value = self.evaluate_value_expression(expr.as_ref())?;
 				println!("{}", value);
 			}
+			VoidExpression::Return(Return { expr }) => {
+				let value = self.evaluate_value_expression(expr.as_ref())?;
+				eprintln!("Currently Unsupported: Return. Value: {}", value);
+			}
 		}
 
 		Ok(ForgeValue::Null)
diff --git a/forge-script-lang/src/runtime/vm/chunk_builder.rs b/forge-script-lang/src/runtime/vm/chunk_builder.rs
index f644c1f9842f0261d6cfafe280b1ee594a729a0e..ef643c18677328fb3567a5773213289878176d31 100644
--- a/forge-script-lang/src/runtime/vm/chunk_builder.rs
+++ b/forge-script-lang/src/runtime/vm/chunk_builder.rs
@@ -124,6 +124,7 @@ impl Visitor for ChunkBuilder {
 			VoidExpression::Import(_) => Err(VmError::chunk_parser("Unsupported expression")),
 			VoidExpression::Export(_) => Err(VmError::chunk_parser("Unsupported expression")),
 			VoidExpression::Print(_) => Err(VmError::chunk_parser("Unsupported expression")),
+			VoidExpression::Return(_) => Err(VmError::chunk_parser("Unsupported expression")),
 		}
 	}
 
diff --git a/forge-script-lang/tests/cases/basic_maths.fs b/forge-script-lang/tests/cases/basic_maths.fs
new file mode 100644
index 0000000000000000000000000000000000000000..53901834588732a4fd99357c7fab6b51c11b66b8
--- /dev/null
+++ b/forge-script-lang/tests/cases/basic_maths.fs
@@ -0,0 +1,4 @@
+2 + 2;
+3 + 3 - 4 * 5;
+3 + (3 - 2);
+4+4+4 + 4   +   4         +               4;
diff --git a/forge-script-lang/tests/cases/conditionals.fs b/forge-script-lang/tests/cases/conditionals.fs
new file mode 100644
index 0000000000000000000000000000000000000000..b43b7998414be1adbd0f8a465f993c0af97f5604
--- /dev/null
+++ b/forge-script-lang/tests/cases/conditionals.fs
@@ -0,0 +1,10 @@
+import { random_float, random_int, random_bool } from "@runtime:random"
+import { truncate } from "@runtime:maths"
+
+let var_a = random_int(0, 10) >= 5;
+let var_b = random_bool();
+let my_var = if if 1 + 2 > 4 { var_a } else { var_b } {
+    truncate(random_float() * 10)
+} else {
+    random_int(100, 200)
+};
\ No newline at end of file
diff --git a/forge-script-lang/tests/cases/declare_items.fs b/forge-script-lang/tests/cases/declare_items.fs
new file mode 100644
index 0000000000000000000000000000000000000000..fee703b570836050401a3c4095189c91539ca812
--- /dev/null
+++ b/forge-script-lang/tests/cases/declare_items.fs
@@ -0,0 +1,19 @@
+let foo;
+let bar = 123 + foo;
+
+fn my_func() {
+    let foo;
+    let bar = 123 + foo;
+};
+
+let other_name = fn some_internal_name(a, b = 2) {
+    a * b
+};
+
+fn has_early_return(a, b, c) {
+    if b {
+        return false;
+    };
+
+    a + c
+}
\ No newline at end of file
diff --git a/forge-script-lang/tests/program_tests.rs b/forge-script-lang/tests/program_tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..40e59fb4e1dc436931aa4c0b3efbb6db36ba0269
--- /dev/null
+++ b/forge-script-lang/tests/program_tests.rs
@@ -0,0 +1,36 @@
+use forge_script_lang::parse::{parse_expression, parse_program};
+use std::path::PathBuf;
+
+fn get_base_path() -> Result<PathBuf, std::io::Error> {
+	let mut cwd = std::env::current_dir()?;
+	let mut path = PathBuf::from(file!());
+	cwd.pop();
+	path.pop();
+	path.push("cases");
+	Ok(cwd.join(path))
+}
+
+fn iterate_files() -> Result<Vec<PathBuf>, std::io::Error> {
+	let base = get_base_path()?;
+	let mut files = Vec::with_capacity(10);
+	for file in std::fs::read_dir(base.display().to_string())? {
+		let entry = file?;
+		files.push(entry.path());
+	}
+
+	Ok(files)
+}
+
+fn read_and_parse(path: PathBuf) -> Result<(), std::io::Error> {
+	let file = std::fs::read_to_string(path)?;
+	parse_program(file.as_str()).expect("Failed to parse");
+	Ok(())
+}
+
+#[test]
+fn parse_all_programs() -> Result<(), std::io::Error> {
+	for file in iterate_files()?.into_iter() {
+		read_and_parse(file)?;
+	}
+	Ok(())
+}