diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs
index 4252995..7319154 100644
--- a/src/compiler/backend/tests.rs
+++ b/src/compiler/backend/tests.rs
@@ -19,7 +19,7 @@ fn test_emit() {
             1.0)))
 ";
     let ast = parse(tokenize(src));
-    let module = meta_compile(&mut ast.unwrap());
+    let module = meta_compile(ast.unwrap()).unwrap();
     let res = spirv_meta(module);
     println!("{}", res);
 }
diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs
new file mode 100644
index 0000000..f88f500
--- /dev/null
+++ b/src/compiler/meta_compile.rs
@@ -0,0 +1,206 @@
+use crate::{
+    compiler::{
+        AddressingModel, Capability, EntryPoint, ExecutionMode, ExecutionModel, Memory,
+        MemoryModel, expect_list, expect_one_of, expect_symbol,
+    },
+    parser::{Ast, Localised, Location},
+};
+
+use super::{
+    BuiltinDecoration, CompilerError, Decoration, Function, GlobalVariable, Import, Module,
+    StorageClass, expect_empty, expect_empty_ast,
+};
+
+pub fn compile_module<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let _cap = expect_one_of(&["Shader"], expect_symbol(list.next(), &loc)?)?;
+    let _exec = expect_one_of(&["Logical"], expect_symbol(list.next(), &loc)?)?;
+    let _memory = expect_one_of(&["GLSL450"], expect_symbol(list.next(), &loc)?)?;
+    module.memory_model = Memory {
+        addressing_model: AddressingModel::Logical,
+        memory_model: MemoryModel::GLSL450,
+    };
+    module.capabilities.push(Capability::Shader);
+    expect_empty_ast(list)
+}
+
+pub fn compile_import<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let name = expect_symbol(list.next(), &loc)?.into_inner();
+    let value = expect_symbol(list.next(), &loc)?.into_inner();
+    module.imports.push(Import { name, value });
+    expect_empty_ast(list)
+}
+
+pub fn compile_bind<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let Localised {
+        location: name_and_type_loc,
+        item: name_and_type,
+    } = expect_list(list.next(), &loc)?;
+    let name_and_type = name_and_type
+        .into_iter()
+        .map(|x| expect_symbol(Some(x), &name_and_type_loc).map(Localised::into_inner))
+        .collect::<Result<String, CompilerError>>()?;
+    let mut name_and_type = name_and_type.split(':').map(|s| s.to_string());
+    // name_and_type is of the name:type format, like foo:f32
+    let name: String = name_and_type
+        .next()
+        .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?;
+    let typ: String = name_and_type
+        .next()
+        .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?;
+    expect_empty(name_and_type, name_and_type_loc)?;
+    let Localised {
+        location: bind_loc,
+        item: bind,
+    } = expect_list(list.next(), &loc)?;
+    let bind: Vec<String> = bind
+        .into_iter()
+        .map(|x| expect_symbol(Some(x), &bind_loc).map(Localised::into_inner))
+        .collect::<Result<_, _>>()?;
+    let bind_name = bind
+        .get(0)
+        .ok_or(CompilerError::ExpectedSymbol(bind_loc.clone()))?;
+
+    let bind_name = match bind_name.as_str() {
+        "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord),
+        "Location" => Decoration::Location(bind.get(1).unwrap().parse::<u32>().unwrap()),
+        b => return Err(CompilerError::UnknownBind(b.to_string(), bind_loc)),
+    };
+    let mut exists = false;
+    for var in &module.globals {
+        if var.name == name {
+            exists = true;
+        }
+    }
+    if !exists {
+        module.globals.push(GlobalVariable {
+            name,
+            typ,
+            storage_class: StorageClass::Undefined,
+            decorations: vec![bind_name],
+        });
+    }
+    expect_empty_ast(list)
+}
+
+pub fn compile_dec<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let Localised {
+        location: name_and_type_loc,
+        item: name_and_type,
+    } = expect_symbol(list.next(), &loc)?;
+    let mut name_and_type = name_and_type.split(':').map(|s| s.to_string());
+    let name: String = name_and_type
+        .next()
+        .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?;
+    let typ: String = name_and_type
+        .next()
+        .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?;
+    expect_empty(name_and_type, name_and_type_loc)?;
+
+    let storage_class = expect_symbol(list.next(), &loc)?;
+    let mut exists = false;
+    for var in &module.globals {
+        if var.name.as_str() == name.as_str() {
+            exists = true;
+        }
+    }
+    let storage_class = match storage_class.as_str() {
+        "Input" => StorageClass::Input,
+        "Output" => StorageClass::Output,
+        _ => StorageClass::Undefined,
+    };
+    if !exists {
+        module.globals.push(GlobalVariable {
+            name: name.to_string(),
+            typ: typ.to_string(),
+            storage_class: storage_class.clone(),
+            decorations: vec![],
+        });
+    }
+    if exists {
+        module
+            .globals
+            .iter_mut()
+            .find(|x| x.name.as_str() == name.as_str())
+            .unwrap()
+            .storage_class = storage_class.clone();
+    }
+    expect_empty_ast(list)
+}
+
+pub fn compile_entry<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let name = expect_symbol(list.next(), &loc)?;
+    let _exec_model = expect_one_of(&["Fragment"], expect_symbol(list.next(), &loc)?)?;
+    let _exec_mode = expect_one_of(&["OriginUpperLeft"], expect_symbol(list.next(), &loc)?)?;
+    let Localised {
+        location: interface_loc,
+        item: interface,
+    } = expect_list(list.next(), &loc)?;
+    let interface: Vec<String> = interface
+        .into_iter()
+        .map(|x| expect_symbol(Some(x), &interface_loc).map(|s| s.into_inner().replace(":", "")))
+        .collect::<Result<_, _>>()?;
+    module.entry_points.push(EntryPoint {
+        execution_model: ExecutionModel::Fragment,
+        execution_mode: ExecutionMode::OriginUpperLeft,
+        name: name.to_string(),
+        interface,
+    });
+
+    expect_empty_ast(list)
+}
+
+pub fn compile_fun<I: Iterator<Item = Ast>>(
+    module: &mut Module,
+    mut list: I,
+    loc: Location,
+) -> Result<(), CompilerError> {
+    let Localised {
+        location: name_loc,
+        item: name,
+    } = expect_list(list.next(), &loc)?;
+    let mut name_it = name.into_iter();
+    let name = expect_symbol(name_it.next(), &name_loc)?;
+    expect_empty(name_it, name_loc)?;
+    let body = list.collect::<Vec<_>>();
+    let location = if let (Some(s), Some(e)) = (
+        body.first().map(|a| a.location()),
+        body.last().map(|a| a.location()),
+    ) {
+        Location::range(s, e)
+    } else {
+        Location::Char { line: 0, col: 0 }
+    };
+    let fun = Function {
+        name: name.to_string(),
+        return_type: "void".to_string(),
+        arguments: vec![],
+        body: Some(vec![]),
+        ast: Some(Ast::List(Localised {
+            location,
+            item: body,
+        })),
+    };
+    module.functions.push(fun);
+
+    Ok(())
+}
diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs
index 17abca9..0238826 100644
--- a/src/compiler/mod.rs
+++ b/src/compiler/mod.rs
@@ -1,6 +1,11 @@
+use meta_compile::{
+    compile_bind, compile_dec, compile_entry, compile_fun, compile_import, compile_module,
+};
+
 use crate::parser::{Ast, Localised, Location};
 
 pub mod backend;
+mod meta_compile;
 
 use std::fmt;
 
@@ -127,215 +132,111 @@ pub struct Module {
     pub imports: Vec<Import>,
 }
 
-pub fn meta_compile(ast: &mut Ast) -> Module {
-    let mut module: Module = Module::default();
+#[derive(Debug)]
+pub enum CompilerError {
+    ExpectedSymbol(Location),
+    ExpectedList(Location),
+    UnknownKeyword(String, Location),
+    ExpectedOneOf(Vec<String>, String, Location),
+    TrailingTokens(Location),
+    UnknownBind(String, Location),
+    ExpectedType(Location),
+}
+
+fn expect_symbol(ast: Option<Ast>, loc: &Location) -> Result<Localised<String>, CompilerError> {
+    match ast {
+        Some(Ast::Symbol(s)) => Ok(s),
+        Some(l) => Err(CompilerError::ExpectedSymbol(l.location().clone())),
+        None => Err(CompilerError::ExpectedSymbol(loc.tail())),
+    }
+}
+
+fn expect_list(ast: Option<Ast>, loc: &Location) -> Result<Localised<Vec<Ast>>, CompilerError> {
     match ast {
-        Ast::Root(root) => {
-            for node in root {
-                match node {
-                    Ast::List(list) => {
-                        let keyword = list[0].clone();
-                        match keyword {
-                            Ast::Symbol(sym) => {
-                                match sym.as_str() {
-                                    "module" => {
-                                        let cap = list[1].clone();
-                                        let exec = list[2].clone();
-                                        let memory = list[3].clone();
-                                        module.memory_model = Memory {
-                                            addressing_model: AddressingModel::Logical,
-                                            memory_model: MemoryModel::GLSL450,
-                                        };
-                                        module.capabilities.push(Capability::Shader);
-                                        assert_eq!(exec.symbol(), Some("Logical".to_string()));
-                                        assert_eq!(memory.symbol(), Some("GLSL450".to_string()));
-                                        assert_eq!(cap.symbol(), Some("Shader".to_string()));
-                                    }
-                                    "import" => {
-                                        let name = match &list[1] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[1]),
-                                        };
-                                        let value = match &list[2] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[2]),
-                                        };
-                                        module.imports.push(Import {
-                                            name: name.to_string(),
-                                            value: value.to_string(),
-                                        });
-                                    }
-                                    "bind" => {
-                                        let name_and_type = match &list[1] {
-                                            Ast::List(l) => l,
-                                            _ => panic!("Expected list! {:?}", list[1]),
-                                        };
-                                        let name_and_type: String = name_and_type
-                                            .iter()
-                                            .map(|x| match x {
-                                                Ast::Symbol(s) => s.to_string(),
-                                                _ => panic!("Expected symbol! {:?}", x),
-                                            })
-                                            .collect();
-                                        // name_and_type is of the name:type format, like foo:f32
-                                        let name: String =
-                                            name_and_type.split(":").collect::<Vec<&str>>()[0]
-                                                .to_string();
-                                        let typ: String =
-                                            name_and_type.split(":").collect::<Vec<&str>>()[1]
-                                                .to_string();
-                                        let bind = match &list[2] {
-                                            Ast::List(l) => l,
-                                            _ => panic!("Expected list! {:?}", list[2]),
-                                        };
-                                        let bind: Vec<String> = bind
-                                            .iter()
-                                            .map(|x| match x {
-                                                Ast::Symbol(s) => s.to_string(),
-                                                _ => panic!("Expected symbol! {:?}", x),
-                                            })
-                                            .collect();
-                                        let bind_name = match bind[0].as_str() {
-                                            "BuiltIn" => {
-                                                Decoration::BuiltIn(BuiltinDecoration::FragCoord)
-                                            }
-                                            "Location" => Decoration::Location(
-                                                bind[1].parse::<u32>().unwrap(),
-                                            ),
-                                            _ => panic!("Unknown bind! {:?}", bind),
-                                        };
-                                        let mut exists = false;
-                                        for var in &module.globals {
-                                            if var.name == name {
-                                                exists = true;
-                                            }
-                                        }
-                                        if !exists {
-                                            module.globals.push(GlobalVariable {
-                                                name: name,
-                                                typ: typ,
-                                                storage_class: StorageClass::Undefined,
-                                                decorations: vec![bind_name],
-                                            });
-                                        }
-                                    }
-                                    "dec" => {
-                                        let name_and_type = match &list[1] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[1]),
-                                        };
-                                        let name: String =
-                                            name_and_type.split(":").collect::<Vec<&str>>()[0]
-                                                .to_string();
-                                        let typ: String =
-                                            name_and_type.split(":").collect::<Vec<&str>>()[1]
-                                                .to_string();
-                                        let storage_class = match &list[2] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[2]),
-                                        };
-                                        let mut exists = false;
-                                        for var in &module.globals {
-                                            if var.name.as_str() == name.as_str() {
-                                                exists = true;
-                                            }
-                                        }
-                                        let storage_class = match storage_class.as_str() {
-                                            "Input" => StorageClass::Input,
-                                            "Output" => StorageClass::Output,
-                                            _ => StorageClass::Undefined,
-                                        };
-                                        if !exists {
-                                            module.globals.push(GlobalVariable {
-                                                name: name.to_string(),
-                                                typ: typ.to_string(),
-                                                storage_class: storage_class.clone(),
-                                                decorations: vec![],
-                                            });
-                                        }
-                                        if exists {
-                                            module
-                                                .globals
-                                                .iter_mut()
-                                                .find(|x| x.name.as_str() == name.as_str())
-                                                .unwrap()
-                                                .storage_class = storage_class.clone();
-                                        }
-                                    }
-                                    "entry" => {
-                                        let name = match &list[1] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[1]),
-                                        };
-                                        let exec_model = match &list[2] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[2]),
-                                        };
-                                        let exec_mode = match &list[3] {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", list[3]),
-                                        };
-                                        let interface = match &list[4] {
-                                            Ast::List(l) => l,
-                                            _ => panic!("Expected list! {:?}", list[4]),
-                                        };
-                                        let interface: Vec<String> = interface
-                                            .iter()
-                                            .map(|x| match x {
-                                                Ast::Symbol(s) => s.to_string().replace(":", ""),
-                                                _ => panic!("Expected symbol! {:?}", x),
-                                            })
-                                            .collect();
-                                        module.entry_points.push(EntryPoint {
-                                            execution_model: ExecutionModel::Fragment,
-                                            execution_mode: ExecutionMode::OriginUpperLeft,
-                                            name: name.to_string(),
-                                            interface: interface,
-                                        });
-                                        assert_eq!(**exec_model, "Fragment");
-                                        assert_eq!(**exec_mode, "OriginUpperLeft");
-                                    }
-                                    "fun" => {
-                                        let name = match &list[1] {
-                                            Ast::List(l) => l[0].clone(),
-                                            _ => panic!("Expected list! {:?}", list[1]),
-                                        };
-                                        let name = match name {
-                                            Ast::Symbol(s) => s,
-                                            _ => panic!("Expected symbol! {:?}", name),
-                                        };
-                                        let body = list[2..].to_vec();
-                                        let location = if let (Some(s), Some(e)) = (
-                                            body.first().map(|a| a.location()).flatten(),
-                                            body.last().map(|a| a.location()).flatten(),
-                                        ) {
-                                            Location::range(s, e)
-                                        } else {
-                                            Location::Char { line: 0, col: 0 }
-                                        };
-                                        let fun = Function {
-                                            name: name.to_string(),
-                                            return_type: "void".to_string(),
-                                            arguments: vec![],
-                                            body: Some(vec![]),
-                                            ast: Some(Ast::List(Localised {
-                                                location,
-                                                item: body,
-                                            })),
-                                        };
-                                        module.functions.push(fun);
-                                    }
-                                    _ => panic!("Unknown keyword! {:?}", sym),
-                                }
-                            }
-                            _ => panic!("List where a keyword was expected! {:?}", keyword),
-                        }
-                    }
-                    _ => panic!("Top-level symbol! {:?}", node),
+        Some(Ast::List(l)) => Ok(l),
+        Some(l) => Err(CompilerError::ExpectedList(l.location().clone())),
+        None => Err(CompilerError::ExpectedList(loc.tail())),
+    }
+}
+
+fn expect_one_of<T>(
+    options: &[T],
+    Localised { location, item }: Localised<String>,
+) -> Result<Localised<String>, CompilerError>
+where
+    T: PartialEq<String> + Into<String> + Clone,
+{
+    if options.iter().find(|e| **e == item).is_some() {
+        Ok(Localised { location, item })
+    } else {
+        Err(CompilerError::ExpectedOneOf(
+            options
+                .iter()
+                .map(|t| Into::<String>::into(t.clone()))
+                .collect(),
+            item,
+            location,
+        ))
+    }
+}
+
+fn expect_empty_ast<I: Iterator<Item = Ast>>(mut iter: I) -> Result<(), CompilerError> {
+    match iter.next() {
+        Some(a) => Err(CompilerError::TrailingTokens(a.location().clone())),
+        None => Ok(()),
+    }
+}
+
+fn expect_empty<T, I: Iterator<Item = T>>(
+    mut iter: I,
+    location: Location,
+) -> Result<(), CompilerError> {
+    match iter.next() {
+        Some(_) => Err(CompilerError::TrailingTokens(location)),
+        None => Ok(()),
+    }
+}
+
+pub fn meta_compile(ast: Ast) -> Result<Module, CompilerError> {
+    let mut module: Module = Module::default();
+    let Ast::Root(root) = ast else {
+        panic!("Non-root ast"); // compiler error, fine to panic
+    };
+    for node in root {
+        let Localised {
+            location,
+            item: list,
+        } = expect_list(Some(node), &Location::All)?;
+        let mut iter = list.into_iter();
+
+        match iter.next() {
+            Some(Ast::Symbol(sym)) => match sym.as_str() {
+                "module" => {
+                    compile_module(&mut module, iter, location)?;
+                }
+                "import" => {
+                    compile_import(&mut module, iter, location)?;
+                }
+                "bind" => {
+                    compile_bind(&mut module, iter, location)?;
+                }
+                "dec" => {
+                    compile_dec(&mut module, iter, location)?;
+                }
+                "entry" => {
+                    compile_entry(&mut module, iter, location)?;
+                }
+                "fun" => {
+                    compile_fun(&mut module, iter, location)?;
+                }
+                _ => {
+                    let Localised { location, item } = sym;
+                    return Err(CompilerError::UnknownKeyword(item, location));
                 }
-            }
+            },
+            Some(l) => return Err(CompilerError::ExpectedSymbol(l.location().clone())),
+            None => return Err(CompilerError::ExpectedSymbol(location)),
         }
-        _ => panic!("Non-root ast"),
     }
-    module
+    Ok(module)
 }
diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs
index 9cbc04c..2a3dcee 100644
--- a/src/compiler/tests.rs
+++ b/src/compiler/tests.rs
@@ -1,4 +1,7 @@
-use crate::parser::{parse, tokenize};
+use crate::{
+    compiler::{CompilerError, meta_compile},
+    parser::{Location, parse, tokenize},
+};
 
 #[test]
 fn test_compile() {
@@ -20,7 +23,7 @@ fn test_compile() {
 ";
     let ast = parse(tokenize(src));
     println!("{:#?}", ast);
-    let module = meta_compile(&mut ast.unwrap());
+    let module = meta_compile(ast.unwrap()).unwrap();
     println!("{:#?}", module);
     let test_module = Module {
         capabilities: vec![Capability::Shader],
@@ -93,3 +96,198 @@ fn test_compile() {
     dbg!(&test_module);
     assert_eq!(module, test_module);
 }
+
+#[test]
+fn expected_symbol() {
+    let src = "
+(module Shader Logical )
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    assert!(matches!(
+        module,
+        Err(CompilerError::ExpectedSymbol(Location::Char {
+            line: 2,
+            col: 24
+        }))
+    ))
+}
+
+#[test]
+fn expected_list() {
+    let src = "
+(module Shader Logical GLSL450)
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun main
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    let Err(CompilerError::ExpectedList(Location::String { lines, cols })) = module else {
+        panic!()
+    };
+    assert_eq!(lines, 9..=9);
+    assert_eq!(cols, 6..=9);
+}
+
+#[test]
+fn unknown_keyword() {
+    let src = "
+(module Shader Logical GLSL450)
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fum (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    let Err(CompilerError::UnknownKeyword(kw, Location::String { lines, cols })) = module else {
+        panic!()
+    };
+    assert_eq!(kw, "fum".to_string());
+    assert_eq!(lines, 9..=9);
+    assert_eq!(cols, 2..=4);
+}
+
+#[test]
+fn expected_one_of() {
+    let src = "
+(module Shader Loical GLSL450)
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    let Err(CompilerError::ExpectedOneOf(l, kw, Location::String { lines, cols })) = module else {
+        panic!()
+    };
+    assert_eq!(l, vec!["Logical".to_string()]);
+    assert_eq!(kw, "Loical".to_string());
+    assert_eq!(lines, 2..=2);
+    assert_eq!(cols, 16..=21);
+}
+
+#[test]
+fn trailing_tokens() {
+    let src = "
+(module Shader Logical GLSL450 GLSL451)
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    let Err(CompilerError::TrailingTokens(Location::String { lines, cols })) = module else {
+        panic!()
+    };
+    assert_eq!(lines, 2..=2);
+    assert_eq!(cols, 32..=38);
+}
+
+#[test]
+fn unknown_bind() {
+    let src = "
+(module Shader Logical GLSL450)
+(import :std GLSL.std.450)
+(bind (frag-coord:*v4f32i) (BuiltIm FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    dbg!(&module);
+    let Err(CompilerError::UnknownBind(b, Location::String { lines, cols })) = module else {
+        panic!()
+    };
+    assert_eq!(b, "BuiltIm");
+    assert_eq!(lines, 4..=4);
+    assert_eq!(cols, 28..=46);
+}
+
+#[test]
+fn expected_type() {
+    let src = "
+(module Shader Logical GLSL450)
+(import :std GLSL.std.450)
+(bind (frag-coord) (BuiltIn FragCoord))
+(bind (out-color:*v4f32o) (Location 0))
+(dec frag-coord:*v4f32i Input)
+(dec out-color:*v4f32o Output)
+(entry main Fragment OriginUpperLeft (:frag-coord :out-color))
+(fun (main)
+  (store-ptr (out-color)
+    (v4f32i (/ (.xy (load-ptr frag-coord))
+               (v2f32 1920.0 1080.0)) 
+            1.0 
+            1.0)))
+";
+    let ast = parse(tokenize(src));
+    println!("{:#?}", ast);
+    let module = meta_compile(ast.unwrap());
+    dbg!(&module);
+    let Err(CompilerError::ExpectedType(Location::Char { line: 4, col: 18 })) = module else {
+        panic!()
+    };
+}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index f7b9a58..550f0ad 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -3,6 +3,7 @@ mod tests;
 
 use std::iter::Peekable;
 use std::ops::{Deref, RangeInclusive};
+use std::usize;
 use std::vec::IntoIter;
 
 #[derive(Clone, Debug)]
@@ -15,6 +16,7 @@ pub enum Location {
         lines: RangeInclusive<usize>,
         cols: RangeInclusive<usize>,
     },
+    All,
 }
 
 #[derive(Debug)]
@@ -42,6 +44,7 @@ impl Location {
         match self {
             Location::Char { line, .. } => *line,
             Location::String { lines, .. } => *lines.start(),
+            Location::All => 0,
         }
     }
 
@@ -49,6 +52,7 @@ impl Location {
         match self {
             Location::Char { col, .. } => *col,
             Location::String { cols, .. } => *cols.start(),
+            Location::All => 0,
         }
     }
 
@@ -56,6 +60,7 @@ impl Location {
         match self {
             Location::Char { line, .. } => *line,
             Location::String { lines, .. } => *lines.end(),
+            Location::All => usize::MAX,
         }
     }
 
@@ -63,6 +68,24 @@ impl Location {
         match self {
             Location::Char { col, .. } => *col,
             Location::String { cols, .. } => *cols.end(),
+            Location::All => usize::MAX,
+        }
+    }
+
+    pub fn tail(&self) -> Self {
+        match self {
+            Location::Char { line, col } => Location::Char {
+                line: *line,
+                col: *col,
+            },
+            Location::String { lines, cols } => Location::Char {
+                line: *lines.end(),
+                col: *cols.end(),
+            },
+            Location::All => Location::Char {
+                line: usize::MAX,
+                col: usize::MAX,
+            },
         }
     }
 }
@@ -73,6 +96,12 @@ pub struct Localised<T: Clone> {
     pub item: T,
 }
 
+impl<T: Clone> Localised<T> {
+    pub fn into_inner(self) -> T {
+        self.item
+    }
+}
+
 impl<T: Clone> Localised<T> {
     #[cfg(test)]
     pub fn dummy_location(item: T) -> Self {
@@ -119,11 +148,11 @@ impl Ast {
         }
     }
 
-    pub fn location(&self) -> Option<&Location> {
+    pub fn location(&self) -> &Location {
         match self {
-            Ast::Symbol(Localised { location, .. }) => Some(location),
-            Ast::List(Localised { location, .. }) => Some(location),
-            Ast::Root(_) => None,
+            Ast::Symbol(Localised { location, .. }) => location,
+            Ast::List(Localised { location, .. }) => location,
+            Ast::Root(_) => &Location::All,
         }
     }
 }