Actually just three programming languages in a trenchcoat
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}