From 9aafb9758f289145e8ca30dfa418615a1242a064 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 20:10:11 +0100 Subject: [PATCH] Refactor and error handling for compiler --- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 206 ++++++++++++++++++++++ src/compiler/mod.rs | 315 ++++++++++++---------------------- src/compiler/tests.rs | 202 +++++++++++++++++++++- src/parser/mod.rs | 37 +++- 5 files changed, 548 insertions(+), 214 deletions(-) create mode 100644 src/compiler/meta_compile.rs 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>( + 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>( + 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>( + 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::>()?; + 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 = bind + .into_iter() + .map(|x| expect_symbol(Some(x), &bind_loc).map(Localised::into_inner)) + .collect::>()?; + 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::().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>( + 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>( + 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 = interface + .into_iter() + .map(|x| expect_symbol(Some(x), &interface_loc).map(|s| s.into_inner().replace(":", ""))) + .collect::>()?; + 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>( + 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::>(); + 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, } -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, Location), + TrailingTokens(Location), + UnknownBind(String, Location), + ExpectedType(Location), +} + +fn expect_symbol(ast: Option, loc: &Location) -> Result, 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, loc: &Location) -> Result>, 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::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[1] - .to_string(); - let bind = match &list[2] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[2]), - }; - let bind: Vec = 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::().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::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[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 = 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( + options: &[T], + Localised { location, item }: Localised, +) -> Result, CompilerError> +where + T: PartialEq + Into + Clone, +{ + if options.iter().find(|e| **e == item).is_some() { + Ok(Localised { location, item }) + } else { + Err(CompilerError::ExpectedOneOf( + options + .iter() + .map(|t| Into::::into(t.clone())) + .collect(), + item, + location, + )) + } +} + +fn expect_empty_ast>(mut iter: I) -> Result<(), CompilerError> { + match iter.next() { + Some(a) => Err(CompilerError::TrailingTokens(a.location().clone())), + None => Ok(()), + } +} + +fn expect_empty>( + mut iter: I, + location: Location, +) -> Result<(), CompilerError> { + match iter.next() { + Some(_) => Err(CompilerError::TrailingTokens(location)), + None => Ok(()), + } +} + +pub fn meta_compile(ast: Ast) -> Result { + 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, cols: RangeInclusive, }, + 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 { pub item: T, } +impl Localised { + pub fn into_inner(self) -> T { + self.item + } +} + impl Localised { #[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, } } }