diff --git a/comb_parse.py b/comb_parse.py
index ce0d7b4..b5b1923 100644
--- a/comb_parse.py
+++ b/comb_parse.py
@@ -6,7 +6,6 @@ from functools import reduce
 from re import compile, Pattern
 
 from lex import Lexeme, tokenize
-from parse import Action
 
 from typing import Any, Callable, Collection, Mapping, Sequence, Tuple, TypeAlias
 
diff --git a/genir.py b/genir.py
new file mode 100644
index 0000000..9cc1a8f
--- /dev/null
+++ b/genir.py
@@ -0,0 +1,24 @@
+from emis_funky_funktions import *
+from typing import *
+
+from silly_thing import *
+from pattern import lex_and_parse_pattern
+from ir import Function, Application, Int, Variable
+
+import json
+
+JsonType: TypeAlias = 'Mapping[str, JsonType] | Sequence[JsonType] | int | str'
+
+def json_to_ir(j: JsonType) -> Expression:
+	if isinstance(j, Mapping):
+		return Function(tuple(
+			#TODO handle parse errors
+			(unwrap_r(lex_and_parse_pattern(k)), json_to_ir(v))
+			for (k, v) in j.items()
+		))
+	elif isinstance(j, str):
+		return Variable(j)
+	elif isinstance(j, Sequence):
+		return Application([json_to_ir(e) for e in j])
+	else:
+		return Int(j)
\ No newline at end of file
diff --git a/ir.py b/ir.py
new file mode 100644
index 0000000..c7f8d42
--- /dev/null
+++ b/ir.py
@@ -0,0 +1,281 @@
+from emis_funky_funktions import *
+
+from typing import Mapping, Sequence, Tuple, TypeAlias
+
+
+Expression: TypeAlias = 'Function | Application | Int | Variable | Builtin'
+Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern | IgnorePattern'
+
+@dataclass(frozen=True)
+class NamePattern:
+	"""
+	A pattern which always succeeds to match, and binds a whole expression to a name
+	"""
+	name: str
+
+	def binds(self, var: str) -> bool:
+		"""
+		Test to see if this pattern binds a given variable
+		"""
+		return var == self.name
+
+	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
+		"""
+		Match an expression against this pattern
+
+		>>> NamePattern('my_var').match(Int(1))
+		Some((('my_var', 1),))
+		"""
+		return Some(((self.name, e),))
+
+	def __repr__(self) -> str:
+		return self.name
+
+@dataclass(frozen=True)
+class IgnorePattern:
+	"""
+	A pattern which always succeeds to match, but binds nothing
+	"""
+
+	def binds(self, var: str) -> bool:
+		"""
+		Test to see if this pattern binds a given variable
+
+		For an `IgnorePattern` this is always false
+		"""
+		return False
+
+	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
+		"""
+		Match an expression against this pattern
+
+		>>> IgnorePattern().match(Int(1))
+		Some(())
+		"""
+		return Some(tuple())
+
+	def __repr__(self) -> str:
+		return '_'
+
+@dataclass(frozen=True)
+class IntPattern:
+	value: int
+
+	def binds(self, var: str) -> bool:
+		"""
+		Test to see if this pattern binds a given variable
+
+		For an `IntPattern` this is always false
+		"""
+		return False
+
+	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
+		"""
+		Match an expression against this pattern
+
+		>>> IntPattern(2).match(Int(1)) is None
+		True
+
+		>>> IntPattern(1).match(Int(1))
+		Some(())
+		"""
+		match e:
+			case Int(v) if v == self.value:
+				return Some(tuple())
+		return None
+
+	def __repr__(self) -> str:
+		return repr(self.value)
+
+@dataclass(frozen=True)
+class SPattern:
+	pred: Pattern
+
+	def binds(self, var: str) -> bool:
+		"""
+		Test to see if this pattern binds a given variable
+		"""
+		return self.pred.binds(var)
+
+	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
+		"""
+		Match an expression against this pattern
+
+		>>> SPattern(NamePattern('n')).match(Int(1))
+		Some((('n', 0),))
+
+		>>> SPattern(NamePattern('n')).match(Int(0)) is None
+		True
+
+		>>> SPattern(SPattern(NamePattern('n'))).match(Int(4))
+		Some((('n', 2),))
+		"""
+		match e:
+			case Int(v) if v > 0:
+				return self.pred.match(Int(v - 1))
+		return None
+
+	def __repr__(self) -> str:
+		return 'S ' + repr(self.pred)
+
+@dataclass(frozen=True)
+class Builtin:
+	name: str
+	f: Callable[[Expression], Option[Expression]]
+
+	def subst(self, expression: Expression, variable: str) -> Expression:
+		return self
+
+	def is_value(self) -> bool:
+		return True
+
+	def step(self) -> Option[Expression]:
+		return None
+
+	def try_apply(self, v: Expression) -> Option[Expression]:
+		return self.f(v)
+
+	def __repr__(self) -> str:
+		return "'" + repr(self.name)[1:-1] + "'"
+
+	@cur2
+	@staticmethod
+	def _PLUS_CONST(i: int, e: Expression) -> Option[Expression]:
+		match e:
+			case Int(v):
+				return Some(Int(i + v))
+		return None
+
+	@staticmethod
+	def _PLUS(e: Expression) -> Option[Expression]:
+		match e:
+			case Int(v):
+				return Some(Builtin(f'+{v}', Builtin._PLUS_CONST(v)))
+		return None
+
+	@staticmethod
+	def PLUS() -> 'Builtin':
+		return Builtin('+', Builtin._PLUS)
+
+	@staticmethod
+	def S() -> 'Builtin':
+		return Builtin('S', Builtin._PLUS_CONST(1))
+
+@dataclass(frozen=True)
+class Function:
+	forms: Sequence[Tuple[Pattern, Expression]]
+
+	def subst(self, expression: Expression, variable: str) -> Expression:
+		return Function([
+			(p, e if p.binds(variable) else e.subst(expression, variable))
+			for (p, e) in self.forms
+		])
+
+	def is_value(self) -> bool:
+		return True
+
+	def step(self) -> Option[Expression]:
+		return None
+
+	def try_apply(self, v: Expression) -> Option[Expression]:
+		match tuple((bindings.val, body) for (pattern, body) in self.forms for bindings in (pattern.match(v),) if bindings is not None):
+			case []:
+				return None
+			case [(bindings, body), *rest]:
+				return Some(subst_all(bindings, body.subst(self, 'recur')))
+		raise Exception('Unreachable')
+
+	def __repr__(self) -> str:
+		return '{ ' + ', '.join('"' + repr(repr(p))[1:-1] + '" : ' + repr(e) for (p, e) in self.forms) + ' }'
+
+@dataclass
+class Application:
+	expressions: Sequence[Expression]
+
+	def subst(self, expression: Expression, variable: str) -> Expression:
+		return Application([
+			e.subst(expression, variable)
+			for e in self.expressions
+		])
+
+	def is_value(self) -> bool:
+		return not len(self.expressions)
+
+	def step(self) -> Option[Expression]:
+		match self.expressions:
+			case []:
+				return None
+			case [e]:
+				return Some(e)
+			case [f, a, *rest]:
+				if f.is_value():
+					if a.is_value():
+						if isinstance(f, Function) or isinstance(f, Builtin):
+							return map_opt(
+								lambda maybe_f_sub: Application([maybe_f_sub, *rest]),
+								f.try_apply(a)
+							)
+						else:
+							return None
+					else:
+						return map_opt(
+							lambda next_a: Application([f, next_a, *rest]),
+							a.step()
+						)
+				else:
+					return map_opt(
+						lambda next_f: Application([next_f, a, *rest]),
+						f.step()
+					)
+		raise Exception('Unreachable')
+
+	def __repr__(self) -> str:
+		return '[ ' + ', '.join(repr(e) for e in self.expressions) + ' ]'
+
+@dataclass
+class Int:
+	value: int
+
+	def subst(self, expression: Expression, variable: str) -> Expression:
+		return self
+
+	def is_value(self) -> bool:
+		return True
+
+	def step(self) -> Option[Expression]:
+		return None
+
+	def __repr__(self) -> str:
+		return str(self.value)
+
+@dataclass
+class Variable:
+	name: str
+
+	def subst(self, expression: Expression, variable: str) -> Expression:
+		if variable == self.name:
+			return expression
+		else:
+			return self
+
+	def is_value(self) -> bool:
+		return False
+
+	def step(self) -> Option[Expression]:
+		match self.name:
+			case '+':
+				return Some(Builtin.PLUS())
+			case 'S':
+				return Some(Builtin.S())
+		return None
+
+	def __repr__(self) -> str:
+		return '"' + repr(self.name)[1:-1] + '"'
+
+def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression:
+	match bindings:
+		case []:
+			return body
+		case [(var, replacement), *rest]:
+			return subst_all(rest, body.subst(replacement, var))
+	raise Exception('Unreachable')
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..29c011f
--- /dev/null
+++ b/main.py
@@ -0,0 +1,8 @@
+from emis_funky_funktions import *
+
+from genir import json_to_ir
+from silly_thing import evaluate
+
+import json, sys
+
+print(evaluate(json_to_ir(json.loads(open(sys.argv[1]).read()))))
\ No newline at end of file
diff --git a/pattern.py b/pattern.py
new file mode 100644
index 0000000..54f6b9b
--- /dev/null
+++ b/pattern.py
@@ -0,0 +1,56 @@
+from emis_funky_funktions import *
+from typing import Collection, Mapping, Sequence, Tuple, TypeAlias
+
+from comb_parse import Parser
+from ir import Pattern, NamePattern, IgnorePattern, IntPattern, SPattern
+from lex import Lexeme, tokenize
+
+from enum import auto, IntEnum
+import re
+
+class PatTok(IntEnum):
+	"""
+	All possible tokens used in the grammar
+	"""
+	Whitespace = auto()
+	Number = auto()
+	Succ = auto()
+	Underscore = auto()
+	Name = auto()
+	Eof = auto()
+
+	def __repr__(self):
+		return self._name_
+
+PATTERN_LEX_TABLE: Collection[Tuple[re.Pattern[str], PatTok]] = [
+	(re.compile(r"\s+"), PatTok.Whitespace),
+	(re.compile(r"\d+"), PatTok.Number),
+	(re.compile(r"S"), PatTok.Succ),
+	(re.compile(r"_"), PatTok.Underscore),
+	(re.compile(r"\w+"), PatTok.Name),
+]
+
+# P := int
+# P := name
+# P := underscore
+# P := S <P>
+
+parse_int:    Parser[Pattern, PatTok] = Parser.token(PatTok.Number).map(Lexeme.get_match).map(int).map(IntPattern)
+parse_name:   Parser[Pattern, PatTok] = Parser.token(PatTok.Name).map(Lexeme.get_match).map(p(NamePattern))
+parse_ignore: Parser[Pattern, PatTok] = Parser.token(PatTok.Underscore).map(lambda _: IgnorePattern())
+parse_succ:   Parser[Pattern, PatTok] = Parser.token(PatTok.Succ).map(k(SPattern)).fapply(Parser.lazy(lambda: parse_P)) #type: ignore
+parse_P:      Parser[Pattern, PatTok] = parse_int.or_(parse_name, parse_ignore, parse_succ)
+
+parse_pattern = parse_P.seq_ignore_tok(PatTok.Eof)
+
+def lex_and_parse_pattern(input: str) -> Result[Pattern, str | Mapping[Lexeme[PatTok], Collection[PatTok]]]:
+	match tokenize(PATTERN_LEX_TABLE, [PatTok.Whitespace], PatTok.Eof, input):
+		case Ok(lexemes):
+			match parse_pattern.parse_(lexemes):
+				case Ok(pattern): # Imagine having a good type system
+					return Ok(pattern)
+				case Err(e):
+					return Err(e)
+		case Err(remainder):
+			return Err(remainder)
+	raise Exception('Unreachable')
diff --git a/silly_thing.py b/silly_thing.py
index 4d85007..fa104aa 100644
--- a/silly_thing.py
+++ b/silly_thing.py
@@ -1,290 +1,11 @@
 from emis_funky_funktions import *
 from typing import Collection, Sequence, TypeAlias
 
+from ir import Expression
+
 from dataclasses import dataclass
 from operator import add
 
-Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern'
-Expression: TypeAlias = 'Function | Application | Int | Variable | Builtin'
-
-@dataclass(frozen=True)
-class NamePattern:
-	"""
-	A pattern which always succeeds to match, and binds a whole expression to a name
-	"""
-	name: str
-
-	def binds(self, var: str) -> bool:
-		"""
-		Test to see if this pattern binds a given variable
-		"""
-		return var == self.name
-
-	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
-		"""
-		Match an expression against this pattern
-
-		>>> NamePattern('my_var').match(Int(1))
-		Some((('my_var', 1),))
-		"""
-		return Some(((self.name, e),))
-
-	def __repr__(self) -> str:
-		return self.name
-
-@dataclass(frozen=True)
-class IgnorePattern:
-	"""
-	A pattern which always succeeds to match, but binds nothing
-	"""
-
-	def binds(self, var: str) -> bool:
-		"""
-		Test to see if this pattern binds a given variable
-
-		For an `IgnorePattern` this is always false
-		"""
-		return False
-
-	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
-		"""
-		Match an expression against this pattern
-
-		>>> IgnorePattern().match(Int(1))
-		Some(())
-		"""
-		return Some(tuple())
-
-	def __repr__(self) -> str:
-		return '_'
-
-@dataclass(frozen=True)
-class IntPattern:
-	value: int
-
-	def binds(self, var: str) -> bool:
-		"""
-		Test to see if this pattern binds a given variable
-
-		For an `IntPattern` this is always false
-		"""
-		return False
-
-	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
-		"""
-		Match an expression against this pattern
-
-		>>> IntPattern(2).match(Int(1)) is None
-		True
-
-		>>> IntPattern(1).match(Int(1))
-		Some(())
-		"""
-		match e:
-			case Int(v) if v == self.value:
-				return Some(tuple())
-		return None
-
-	def __repr__(self) -> str:
-		return repr(self.value)
-
-@dataclass(frozen=True)
-class SPattern:
-	pred: Pattern
-
-	def binds(self, var: str) -> bool:
-		"""
-		Test to see if this pattern binds a given variable
-		"""
-		return self.pred.binds(var)
-
-	def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]:
-		"""
-		Match an expression against this pattern
-
-		>>> SPattern(NamePattern('n')).match(Int(1))
-		Some((('n', 0),))
-
-		>>> SPattern(NamePattern('n')).match(Int(0)) is None
-		True
-
-		>>> SPattern(SPattern(NamePattern('n'))).match(Int(4))
-		Some((('n', 2),))
-		"""
-		match e:
-			case Int(v) if v > 0:
-				return self.pred.match(Int(v - 1))
-		return None
-
-	def __repr__(self) -> str:
-		return 'S ' + repr(self.pred)
-
-@dataclass(frozen=True)
-class Builtin:
-	name: str
-	f: Callable[[Expression], Option[Expression]]
-
-	def subst(self, expression: Expression, variable: str) -> Expression:
-		return self
-
-	def is_value(self) -> bool:
-		return True
-
-	def step(self) -> Option[Expression]:
-		return None
-
-	def try_apply(self, v: Expression) -> Option[Expression]:
-		return self.f(v)
-
-	def __repr__(self) -> str:
-		return '"' + repr(self.name)[1:-1] + '"'
-
-	@cur2
-	@staticmethod
-	def _PLUS_CONST(i: int, e: Expression) -> Option[Expression]:
-		match e:
-			case Int(v):
-				return Some(Int(i + v))
-		return None
-
-	@staticmethod
-	def _PLUS(e: Expression) -> Option[Expression]:
-		match e:
-			case Int(v):
-				return Some(Builtin(f'+{v}', Builtin._PLUS_CONST(v)))
-		return None
-
-	@staticmethod
-	def PLUS() -> 'Builtin':
-		return Builtin('+', Builtin._PLUS)
-
-	@staticmethod
-	def S() -> 'Builtin':
-		return Builtin('S', Builtin._PLUS_CONST(1))
-
-@dataclass(frozen=True)
-class Function:
-	forms: Sequence[Tuple[Pattern, Expression]]
-
-	def subst(self, expression: Expression, variable: str) -> Expression:
-		return Function([
-			(p, e if p.binds(variable) else e.subst(expression, variable))
-			for (p, e) in self.forms
-		])
-
-	def is_value(self) -> bool:
-		return True
-
-	def step(self) -> Option[Expression]:
-		return None
-
-	def try_apply(self, v: Expression) -> Option[Expression]:
-		match self.forms:
-			case []:
-				return None
-			case [(pattern, body), *rest]:
-				match pattern.match(v):
-					case Some(bindings):
-						return Some(subst_all(bindings, body.subst(self, 'recur')))
-					case None:
-						return Function(rest).try_apply(v)
-		raise Exception('Unreachable')
-
-	def __repr__(self) -> str:
-		return '{ ' + ', '.join('"' + repr(repr(p))[1:-1] + '" : ' + repr(e) for (p, e) in self.forms) + ' }'
-
-@dataclass
-class Application:
-	expressions: Sequence[Expression]
-
-	def subst(self, expression: Expression, variable: str) -> Expression:
-		return Application([
-			e.subst(expression, variable)
-			for e in self.expressions
-		])
-
-	def is_value(self) -> bool:
-		return not len(self.expressions)
-
-	def step(self) -> Option[Expression]:
-		match self.expressions:
-			case []:
-				return None
-			case [e]:
-				return Some(e)
-			case [f, a, *rest]:
-				if f.is_value():
-					if a.is_value():
-						if isinstance(f, Function) or isinstance(f, Builtin):
-							return map_opt(
-								lambda maybe_f_sub: Application([maybe_f_sub, *rest]),
-								f.try_apply(a)
-							)
-						else:
-							return None
-					else:
-						return map_opt(
-							lambda next_a: Application([f, next_a, *rest]),
-							a.step()
-						)
-				else:
-					return map_opt(
-						lambda next_f: Application([next_f, a, *rest]),
-						f.step()
-					)
-		raise Exception('Unreachable')
-
-	def __repr__(self) -> str:
-		return '[ ' + ', '.join(repr(e) for e in self.expressions) + ' ]'
-
-@dataclass
-class Int:
-	value: int
-
-	def subst(self, expression: Expression, variable: str) -> Expression:
-		return self
-
-	def is_value(self) -> bool:
-		return True
-
-	def step(self) -> Option[Expression]:
-		return None
-
-	def __repr__(self) -> str:
-		return str(self.value)
-
-@dataclass
-class Variable:
-	name: str
-
-	def subst(self, expression: Expression, variable: str) -> Expression:
-		if variable == self.name:
-			return expression
-		else:
-			return self
-
-	def is_value(self) -> bool:
-		return False
-
-	def step(self) -> Option[Expression]:
-		match self.name:
-			case '+':
-				return Some(Builtin.PLUS())
-			case 'S':
-				return Some(Builtin.S())
-		return None
-
-	def __repr__(self) -> str:
-		return '"' + repr(self.name)[1:-1] + '"'
-
-def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression:
-	match bindings:
-		case []:
-			return body
-		case [(var, replacement), *rest]:
-			return subst_all(rest, body.subst(replacement, var))
-	raise Exception('Unreachable')
-
 def evaluate(expr: Expression) -> Expression:
 	"""
 	>>> funktion = Function((