diff --git a/use-based-refs/interpreter/README.md b/use-based-refs/interpreter/README.md
index 1febbde7b6581ef514864ef8c98895c83df9a65f..60f7666b51511fbd33e00113ed82d6727e19b20b 100644
--- a/use-based-refs/interpreter/README.md
+++ b/use-based-refs/interpreter/README.md
@@ -22,13 +22,18 @@ Right now, the interpreter only executes a program hardcoded in `main`. Change t
     - [x] References
     - [x] Reference types
     - [x] Reference types in type annotations
-    - [ ] Allow for `&*` reborrowing
+    - [ ] Allow `&*` reborrowing
+    - [ ] Allow calling through references as in `(*foo)(arg)`
     - [x] Function definitions
-    - [ ] Function calls
+    - [x] Function calls
+        - [x] Parse error?
     - [x] Allocations
     - [x] Dereference -- read
     - [x] Dereference -- write
-- [ ] Add tests for typechecking functions
+    - [ ] Builtins
+    - [ ] `NoAlias` specification
+    - [ ] Alias checking, of course 
+- [ ] Add tests for typechecking functions/function calls
 
 ## Design decisions to make
 
diff --git a/use-based-refs/interpreter/src/eval.rs b/use-based-refs/interpreter/src/eval.rs
index cba40440c84690972b95e985fd2f73337a3ae880..e44715a70077bb7d811c69b70f4cdced31ad7888 100644
--- a/use-based-refs/interpreter/src/eval.rs
+++ b/use-based-refs/interpreter/src/eval.rs
@@ -40,6 +40,7 @@ impl std::fmt::Display for Value {
     }
 }
 
+#[derive(Debug, PartialEq, Eq, Clone)]
 struct Context {
     names: HashMap<String, Value>,
     // Index serves as address
@@ -154,6 +155,33 @@ fn eval_ctx(ast: Node, ctx: &mut Context) -> Result<Value> {
             );
             Value::Unit
         }
+        Node::Call { ident, args } => {
+            // Eager evaluation of the arguments
+            let mut evals = Vec::with_capacity(args.len());
+            for arg in args {
+                evals.push(eval_ctx(arg, ctx)?)
+            }
+
+            // Look up function
+            let Value::Func {
+                name: _,
+                args,
+                body,
+            } = ctx
+                .names
+                .get(&ident)
+                .ok_or(Error::Name(format!("Function `{ident}` not defined")))?
+            else {
+                return Err(Error::Type(format!("`{ident}` is not a function.")));
+            };
+
+            // Create new context for evaluating the function body,
+            // with the (evaluated) arguments added.
+            let mut ctx_inner = ctx.clone();
+            ctx_inner.names.extend(args.iter().cloned().zip(evals));
+
+            eval_ctx(body.as_ref().clone(), &mut ctx_inner)?
+        }
     })
 }
 
diff --git a/use-based-refs/interpreter/src/grammar.pest b/use-based-refs/interpreter/src/grammar.pest
index de53893c2f789bfa06d45e3dc7ffac82833b67dd..7f6fb51725b57746c39b248546c1871ef9c71b6f 100644
--- a/use-based-refs/interpreter/src/grammar.pest
+++ b/use-based-refs/interpreter/src/grammar.pest
@@ -19,7 +19,8 @@ Seq = { (Expr ~ ";")* ~ Expr }
 /// An expression.
 // I must put DerefWrite first because of shenanigans;
 // I don't know the reason, but it fails to parse otherwise.
-Expr = _{ DerefWrite | Value | TypedLet | Let | Func | Ident | Block | DerefRead }
+Expr = _{ DerefWrite | Value | TypedLet | Let
+        | Func | Call | Ident | Block | DerefRead  }
 
 Value = _{ Unit | Num | Ref }
 
@@ -29,8 +30,11 @@ Ref = { "&" ~ (Value | Ident)}
 DerefRead = {"*" ~ Expr }
 DerefWrite = { Ident ~ ":=" ~ Expr }
 
-Func = { "fn" ~ Ident ~ "(" ~ ArgList ~ ")" ~ "->" ~ Type ~ "=" ~ Expr }
-ArgList = { (TypedIdent ~ ",")* ~ TypedIdent? }
+Func = { "fn" ~ Ident ~ "(" ~ Params ~ ")" ~ "->" ~ Type ~ "=" ~ Expr }
+Params = { (TypedIdent ~ ",")* ~ TypedIdent? }
+// We can currently only call functions from their identifier.
+Call = { Ident ~ "(" ~ Args ~ ")" }
+Args = { (Expr ~ ",")* ~ Expr? }
 
 /// Add the identifier to to context
 ///
diff --git a/use-based-refs/interpreter/src/lib.rs b/use-based-refs/interpreter/src/lib.rs
index 43d5aeee1018f5756d78e1199f078e89106b357c..917c2c9e050cf4a7bcfba4555f1d0ddea6e619a1 100644
--- a/use-based-refs/interpreter/src/lib.rs
+++ b/use-based-refs/interpreter/src/lib.rs
@@ -37,6 +37,10 @@ pub enum Node {
         ty: typecheck::Type,
         body: Box<Node>,
     },
+    Call {
+        ident: String,
+        args: Vec<Node>,
+    },
 }
 
 /// Generalized interpreter error.
diff --git a/use-based-refs/interpreter/src/main.rs b/use-based-refs/interpreter/src/main.rs
index 9d66e1b1653e0f7c9a366e35daa2a34216ac1762..0a109b5ea89f0ba2366ac9cb25389d5bc65971c7 100644
--- a/use-based-refs/interpreter/src/main.rs
+++ b/use-based-refs/interpreter/src/main.rs
@@ -3,7 +3,7 @@
 use interpreter::{ast, eval, typecheck};
 
 fn main() {
-    let program = "fn fst(x: Num, y: Num) -> Num = x; fst";
+    let program = "fn infinite() -> Num = {infinite()}; infinite()";
 
     match ast(program).and_then(typecheck).and_then(eval) {
         Ok(val) => println!("{val}"),
diff --git a/use-based-refs/interpreter/src/parser.rs b/use-based-refs/interpreter/src/parser.rs
index ae86857a1b95b44846e9fc2843b1aec041a083c3..20cf53f9c7813953c2243a39fabe553aa7c2a586 100644
--- a/use-based-refs/interpreter/src/parser.rs
+++ b/use-based-refs/interpreter/src/parser.rs
@@ -41,9 +41,15 @@ pub fn ast(input: &str) -> Result<Node> {
                     pair.as_str()
                 )));
             }
-            Rule::ArgList => {
+            Rule::Params => {
                 return Err(Error::General(format!(
-                    "ArgLust `{}` in unexpected position",
+                    "Params `{}` in unexpected position",
+                    pair.as_str()
+                )))
+            }
+            Rule::Args => {
+                return Err(Error::General(format!(
+                    "Args `{}` in unexpected position",
                     pair.as_str()
                 )))
             }
@@ -135,7 +141,7 @@ pub fn ast(input: &str) -> Result<Node> {
                     .to_string();
                 let args: Result<Vec<(String, crate::typecheck::Type)>> = it
                     .next()
-                    .expect("Function definition always has an arglist")
+                    .expect("Function definition always has params")
                     .into_inner()
                     .map(|p| {
                         // TODO: Maybe this `should` be handled by a specialized match arm?
@@ -161,6 +167,22 @@ pub fn ast(input: &str) -> Result<Node> {
                     body,
                 }
             }
+            Rule::Call => {
+                let mut it = pair.into_inner();
+                let ident = it
+                    .next()
+                    .expect("Function call must have an identifier")
+                    .as_str()
+                    .to_string();
+                let args: Result<_> = it
+                    .next()
+                    .expect("Function call must have an argument list")
+                    .into_inner()
+                    .map(ast_pair)
+                    .collect();
+
+                Node::Call { ident, args: args? }
+            }
         })
     }
     let pair = tokenize(input)?;
diff --git a/use-based-refs/interpreter/src/typecheck.rs b/use-based-refs/interpreter/src/typecheck.rs
index be83a314868e01f2ac49d4259c357aa956d944fa..1327826dd579e347fdfd6e8e7445fac43d7fdde9 100644
--- a/use-based-refs/interpreter/src/typecheck.rs
+++ b/use-based-refs/interpreter/src/typecheck.rs
@@ -61,6 +61,7 @@ impl std::fmt::Display for Type {
 }
 
 /// Typecheck given AST using given context.
+#[allow(clippy::too_many_lines)]
 fn typecheck_ctx(ast: &Node, ctx: &mut Context) -> Result<Type> {
     Ok(match &ast {
         Node::Num(_) => Type::Num,
@@ -145,6 +146,35 @@ fn typecheck_ctx(ast: &Node, ctx: &mut Context) -> Result<Type> {
             }
             Type::Unit
         }
+        #[allow(clippy::manual_let_else)] // For unpacking `(tys, func_ty)`
+        Node::Call { ident, args } => {
+            let (tys, func_ty) = if let Type::Func { args, ty } = ctx
+                .idents
+                .get(ident)
+                .ok_or(Error::Name(format!("Function `{ident}` not found.")))?
+            {
+                (args, ty)
+            } else {
+                return Err(Error::Type(format!("`{ident}` is not a function.")));
+            };
+            if args.len() != tys.len() {
+                return Err(Error::Type(format!(
+                    "`{ident} takes {} arguments, got {}",
+                    tys.len(),
+                    args.len()
+                )));
+            }
+            for (arg, ty) in args.iter().zip(tys) {
+                let arg_ty = typecheck_ctx(arg, &mut ctx.clone())?;
+                if ty != &arg_ty {
+                    return Err(Error::Type(format!(
+                        "Argument has type `{arg_ty}`, expected `{ty}`."
+                    )));
+                }
+            }
+
+            *func_ty.clone()
+        }
     })
 }