Actually just three programming languages in a trenchcoat
at main 168 lines 8.7 kB view raw
1use super::*; 2use crate::{Parser, Spanned, TokenPattern}; 3use source_span::Span; 4use trilogy_scanner::TokenType::*; 5 6/// The various items that can be defined in a Trilogy module. 7#[derive(Clone, Debug, Spanned)] 8pub enum DefinitionItem { 9 /// An inline module definition. 10 Type(Box<TypeDefinition>), 11 /// An external (imported) module definition. 12 Import(Box<ImportDefinition>), 13 /// A procedure definition. 14 Procedure(Box<ProcedureDefinition>), 15 /// An external procedure definition. 16 ExternalProcedure(Box<ExternalProcedureDefinition>), 17 /// A constant definition. 18 Constant(Box<SlotDefinition>), 19 /// A function definition. 20 Function(Box<FunctionDefinition>), 21 /// A rule definition. 22 Rule(Box<RuleDefinition>), 23 /// An export declaration. 24 Export(Box<ExportDefinition>), 25 /// A test definition. 26 Test(Box<TestDefinition>), 27} 28 29/// A definition in a Trilogy program. 30/// 31/// ```trilogy 32/// ## Documentation 33/// proc definition!() {} 34/// ``` 35#[derive(Clone, Debug)] 36pub struct Definition { 37 pub documentation: Option<Documentation>, 38 pub item: DefinitionItem, 39 pub span: Span, 40} 41 42impl Definition { 43 fn parse_until( 44 parser: &mut Parser, 45 until_pattern: impl TokenPattern, 46 ) -> SyntaxResult<Option<Self>> { 47 let documentation = Documentation::parse_outer(parser); 48 49 parser.peek(); 50 let is_line_start = parser.is_line_start; 51 if until_pattern.matches(parser.peek()) { 52 if let Some(documentation) = documentation { 53 let error = SyntaxError::new( 54 documentation.span(), 55 "outer documentation comment must precede the item it documents", 56 ); 57 parser.error(error.clone()); 58 return Err(error); 59 } else { 60 return Ok(None); 61 } 62 } 63 64 if !is_line_start { 65 let error = SyntaxError::new( 66 parser.peek().span, 67 "definitions must be separated by line breaks", 68 ); 69 parser.error(error); 70 } 71 72 let token = parser.peek(); 73 let item = match token.token_type { 74 KwType => { 75 let head = TypeHead::parse(parser)?; 76 if let Err(token) = parser.check(OBrace) { 77 let error = SyntaxError::new( 78 token.span, 79 "only identifiers are permitted in a type definition", 80 ); 81 parser.error(error.clone()); 82 return Err(error); 83 } 84 DefinitionItem::Type(Box::new(TypeDefinition::parse(parser, head)?)) 85 } 86 KwImport => DefinitionItem::Import(Box::new(ImportDefinition::parse(parser)?)), 87 KwExport => DefinitionItem::Export(Box::new(ExportDefinition::parse(parser)?)), 88 KwSlot => DefinitionItem::Constant(Box::new(SlotDefinition::parse(parser)?)), 89 KwRule => DefinitionItem::Rule(Box::new(RuleDefinition::parse(parser)?)), 90 KwProc => DefinitionItem::Procedure(Box::new(ProcedureDefinition::parse(parser)?)), 91 KwExtern => DefinitionItem::ExternalProcedure(Box::new( 92 ExternalProcedureDefinition::parse(parser)?, 93 )), 94 KwFunc => DefinitionItem::Function(Box::new(FunctionDefinition::parse(parser)?)), 95 KwTest => DefinitionItem::Test(Box::new(TestDefinition::parse(parser)?)), 96 DocInner => { 97 let error = SyntaxError::new( 98 token.span, 99 "inner documentation is only supported at the top of a document", 100 ); 101 parser.error(error.clone()); 102 return Err(error); 103 } 104 _ => { 105 let error = SyntaxError::new(token.span, "unexpected token in module body"); 106 parser.error(error.clone()); 107 return Err(error); 108 } 109 }; 110 111 let span = match &documentation { 112 Some(documentation) => documentation.span().union(item.span()), 113 None => item.span(), 114 }; 115 116 Ok(Some(Self { 117 documentation, 118 item, 119 span, 120 })) 121 } 122 123 pub(crate) fn parse_in_document(parser: &mut Parser) -> SyntaxResult<Option<Self>> { 124 Self::parse_until(parser, EndOfFile) 125 } 126 127 pub(crate) fn parse_in_module(parser: &mut Parser) -> SyntaxResult<Option<Self>> { 128 Self::parse_until(parser, [EndOfFile, CBrace]) 129 } 130} 131 132impl Spanned for Definition { 133 fn span(&self) -> Span { 134 self.span 135 } 136} 137 138#[cfg(test)] 139mod test { 140 use super::*; 141 142 test_parse!(def_proc: "proc hello!() {}" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Procedure(..), .. })); 143 test_parse!(def_proc_in_module: "proc hello!() {}" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Procedure(..), .. })); 144 test_parse!(def_func: "func hello x = x" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Function(..), .. })); 145 test_parse!(def_func_in_module: "func hello x = x" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Function(..), .. })); 146 test_parse!(def_fact: "rule hello(a, b)" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Rule(..), .. })); 147 test_parse!(def_rule: "rule hello(a, b) <- x(a) and y(b)" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Rule(..), .. })); 148 test_parse!(def_fact_in_module: "rule hello(a, b)" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Rule(..), .. })); 149 test_parse!(def_rule_in_module: "rule hello(a, b) <- x(a) and y(b)" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Rule(..), .. })); 150 test_parse!(def_module: "type X {}" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Type(..), .. })); 151 test_parse!(def_module_in_module: "type X {}" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Type(..), .. })); 152 test_parse!(def_external_module: "import \"./hello.tri\" as hello" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Import(..), .. })); 153 test_parse!(def_external_module_in_module: "import \"./hello.tri\" as hello" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Import(..), .. })); 154 test_parse!(def_export: "export a, b, c" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Export(..), .. })); 155 test_parse!(def_export_in_module: "export a, b, c" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Export(..), .. })); 156 test_parse!(def_test: "test \"hello\" {}" => Definition::parse_in_document => Some(Definition { item: DefinitionItem::Test(..), .. })); 157 test_parse!(def_test_in_module: "test \"hello\" {}" => Definition::parse_in_module => Some(Definition { item: DefinitionItem::Test(..), .. })); 158 test_parse!(def_documented: "## Hello this is a module\ntype A {}" => Definition::parse_in_document => Some(Definition { documentation: Some(Documentation { .. }), item: DefinitionItem::Type(..), .. })); 159 test_parse!(def_documented_in_module: "## Hello this is a module\ntype A {}" => Definition::parse_in_module => Some(Definition { documentation: Some(Documentation { .. }), item: DefinitionItem::Type(..), .. })); 160 test_parse!(def_nothing: "" => Definition::parse_in_document => None); 161 test_parse!(def_nothing_in_module: "" => Definition::parse_in_module => None); 162 test_parse_error!(def_documented_nothing: "## Hello this is a doc for nothing" => Definition::parse_in_document => "outer documentation comment must precede the item it documents"); 163 test_parse_error!(def_documented_nothing_in_module: "## Hello this is a doc for nothing" => Definition::parse_in_module => "outer documentation comment must precede the item it documents"); 164 test_parse_error!(def_documented_inner: "#! Hello this is a module\ntype A {}" => Definition::parse_in_document => "inner documentation is only supported at the top of a document"); 165 test_parse_error!(def_documented_inner_in_module: "#! Hello this is a module\ntype A {}" => Definition::parse_in_module => "inner documentation is only supported at the top of a document"); 166 test_parse_error!(def_no_keyword: "hello x = y" => Definition::parse_in_document => "unexpected token in module body"); 167 test_parse_error!(def_no_keyword_in_module: "hello x = y" => Definition::parse_in_module => "unexpected token in module body"); 168}