Skip to content

Commit 5ab9dfa

Browse files
authored
feat: annotations (#9)
```rs @some_annotation fn my_thing() { // ... } @some_annotation(value) trait MyTrait { // ... } ```
2 parents cea4429 + a4b82ec commit 5ab9dfa

21 files changed

Lines changed: 439 additions & 35 deletions

src/ir/ast.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ create_id!(FunctionId);
1010
create_id!(StatementId);
1111
create_id!(AstTypeId);
1212
create_id!(TraitId);
13+
create_id!(AnnotationId);
1314

1415
#[derive(Clone, Debug)]
1516
pub struct Ast {
@@ -18,6 +19,7 @@ pub struct Ast {
1819
pub blocks: IndexedVec<BlockId, Block>,
1920
pub statements: IndexedVec<StatementId, Statement>,
2021
pub expressions: IndexedVec<ExpressionId, Expression>,
22+
pub annotations: IndexedVec<AnnotationId, Annotation>,
2123

2224
pub types: IndexedVec<AstTypeId, AstType>,
2325

@@ -35,6 +37,7 @@ impl Ast {
3537
blocks: IndexedVec::new(),
3638
statements: IndexedVec::new(),
3739
expressions: IndexedVec::new(),
40+
annotations: IndexedVec::new(),
3841
types: IndexedVec::new(),
3942
item_functions: Vec::new(),
4043
traits: IndexedVec::new(),
@@ -75,6 +78,14 @@ impl Index<StatementId> for Ast {
7578
}
7679
}
7780

81+
impl Index<AnnotationId> for Ast {
82+
type Output = Annotation;
83+
84+
fn index(&self, index: AnnotationId) -> &Self::Output {
85+
&self.annotations[index]
86+
}
87+
}
88+
7889
impl Index<AstTypeId> for Ast {
7990
type Output = AstType;
8091

@@ -102,6 +113,16 @@ pub enum AstType {
102113
Tuple(Vec<AstTypeId>),
103114
}
104115

116+
/// An annotation attached to an item.
117+
#[derive(Clone, Debug)]
118+
#[expect(dead_code, reason = "annotations not used yet")]
119+
pub struct Annotation {
120+
/// Key of the annotation.
121+
pub key: StringId,
122+
/// Value of the annotation, which may or may not be present.
123+
pub value: Option<StringId>,
124+
}
125+
105126
mod function {
106127
use super::*;
107128

@@ -113,7 +134,9 @@ mod function {
113134
}
114135

115136
#[derive(Clone, Debug)]
137+
#[expect(dead_code, reason = "annotations not used yet")]
116138
pub struct FunctionDeclaration {
139+
pub annotations: Vec<AnnotationId>,
117140
pub signature: FunctionSignature,
118141
pub body: BlockId,
119142
}
@@ -289,6 +312,9 @@ mod expression {
289312

290313
#[derive(Clone, Debug)]
291314
pub struct Trait {
315+
/// Annotations attached to this trait.
316+
#[expect(dead_code, reason = "annotations not used yet")]
317+
pub annotations: Vec<AnnotationId>,
292318
/// Original name of the trait.
293319
pub name: StringId,
294320
/// Methods defined within the trait.
@@ -297,6 +323,9 @@ pub struct Trait {
297323

298324
#[derive(Clone, Debug)]
299325
pub struct TraitImplementation {
326+
/// Annotations attached to this trait implementation.
327+
#[expect(dead_code, reason = "annotations not used yet")]
328+
pub annotations: Vec<AnnotationId>,
300329
pub trait_name: StringId,
301330
pub target_ty: AstTypeId,
302331
pub methods: BTreeMap<StringId, FunctionId>,

src/ir/cst.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,56 @@ impl Program {
1919
pub const fn new() -> Self {
2020
Self { items: Vec::new() }
2121
}
22-
23-
pub fn add_function_declaration(&mut self, function_declaration: FunctionDeclaration) {
24-
self.items
25-
.push(Item::FunctionDeclaration(function_declaration));
26-
}
27-
28-
pub fn add_trait_declaration(&mut self, trait_declaration: TraitDeclaration) {
29-
self.items.push(Item::TraitDeclaration(trait_declaration));
30-
}
31-
32-
pub fn add_trait_implementation(&mut self, trait_implementation: TraitImplementation) {
33-
self.items
34-
.push(Item::TraitImplementation(trait_implementation));
35-
}
3622
}
3723

3824
/// A node which may appear at the top-level of a program.
3925
#[derive(Clone, Debug)]
40-
pub enum Item {
26+
pub struct Item {
27+
/// Annotations attached to this item.
28+
pub annotations: Vec<Annotation>,
29+
/// The actual item.
30+
pub kind: ItemKind,
31+
}
32+
33+
#[derive(Clone, Debug)]
34+
pub enum ItemKind {
4135
TraitDeclaration(TraitDeclaration),
4236
TraitImplementation(TraitImplementation),
4337
FunctionDeclaration(FunctionDeclaration),
4438
}
4539

40+
/// An annotation the source, which may or may not have a value.
41+
///
42+
/// ```
43+
/// @some_annotation
44+
///
45+
/// @some_annotation(with_value)
46+
/// ```
47+
#[derive(Clone, Debug)]
48+
pub struct Annotation {
49+
#[expect(dead_code, reason = "token field")]
50+
pub tok_at: tok::At,
51+
/// Key of the annotation.
52+
pub key: tok::Ident,
53+
/// Value of the annotation.
54+
pub value: AnnotationValue,
55+
}
56+
57+
/// Value for an annotation.
58+
#[derive(Clone, Debug)]
59+
pub enum AnnotationValue {
60+
/// No value.
61+
None,
62+
/// Value within parenthesis.
63+
Value {
64+
#[expect(dead_code, reason = "token field")]
65+
tok_l_parenthesis: tok::LParenthesis,
66+
value: tok::Ident,
67+
#[expect(dead_code, reason = "token field")]
68+
tok_r_parenthesis: tok::RParenthesis,
69+
},
70+
}
71+
4672
/// Trait declaration.
4773
///
4874
/// ```

src/lex/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ impl<'src> Lexer<'src> {
189189
_ => Tok::Bang,
190190
}
191191
}
192+
'@' => {
193+
self.expect_char('@');
194+
Tok::At
195+
}
192196
'=' => {
193197
self.expect_char('=');
194198

@@ -341,6 +345,8 @@ mod test {
341345
#[case("::", &[Tok::ColonColon, Tok::Eof])]
342346
#[case(";", &[Tok::SemiColon, Tok::Eof])]
343347
#[case(",", &[Tok::Comma, Tok::Eof])]
348+
#[case("!", &[Tok::Bang, Tok::Eof])]
349+
#[case("@", &[Tok::At, Tok::Eof])]
344350
#[case("==", &[Tok::EqEq, Tok::Eof])]
345351
#[case("!=", &[Tok::BangEq, Tok::Eof])]
346352
#[case(">=", &[Tok::GtEq, Tok::Eof])]

src/lex/tok.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ toks! {
113113
Dot => ".",
114114
Comma => ",",
115115
Bang => "!",
116+
At => "@",
116117

117118
EqEq => "==",
118119
BangEq => "!=",

src/passes/ast_gen.rs

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@ impl<'ctx, 'cst> Pass<'ctx, 'cst> for AstGen<'ctx> {
1717
let mut ast_gen = Self::new(ctx);
1818

1919
for item in &cst.items {
20-
match item {
21-
cst::Item::FunctionDeclaration(function_declaration) => {
22-
ast_gen.lower_item_function(function_declaration);
20+
let annotations = item
21+
.annotations
22+
.iter()
23+
.map(|annotation| ast_gen.lower_annotation(annotation))
24+
.collect();
25+
26+
match &item.kind {
27+
cst::ItemKind::FunctionDeclaration(function_declaration) => {
28+
ast_gen.lower_item_function(function_declaration, annotations);
2329
}
24-
cst::Item::TraitDeclaration(trait_declaration) => {
25-
ast_gen.lower_trait_declaration(trait_declaration);
30+
cst::ItemKind::TraitDeclaration(trait_declaration) => {
31+
ast_gen.lower_trait_declaration(trait_declaration, annotations);
2632
}
27-
cst::Item::TraitImplementation(trait_implementation) => {
28-
ast_gen.lower_trait_implementation(trait_implementation);
33+
cst::ItemKind::TraitImplementation(trait_implementation) => {
34+
ast_gen.lower_trait_implementation(trait_implementation, annotations);
2935
}
3036
}
3137
}
@@ -42,16 +48,38 @@ impl<'ctx> AstGen<'ctx> {
4248
}
4349
}
4450

45-
fn lower_function(&mut self, function: &cst::FunctionDeclaration) -> FunctionId {
51+
fn lower_annotation(&mut self, annotation: &cst::Annotation) -> AnnotationId {
52+
let annotation = Annotation {
53+
key: self.ctx.strings.intern(&annotation.key.0),
54+
value: match &annotation.value {
55+
cst::AnnotationValue::None => None,
56+
cst::AnnotationValue::Value { value, .. } => {
57+
Some(self.ctx.strings.intern(&value.0))
58+
}
59+
},
60+
};
61+
self.ast.annotations.insert(annotation)
62+
}
63+
64+
fn lower_function(
65+
&mut self,
66+
function: &cst::FunctionDeclaration,
67+
annotations: Vec<AnnotationId>,
68+
) -> FunctionId {
4669
let function_declaration = FunctionDeclaration {
70+
annotations,
4771
signature: self.lower_function_signature(&function.signature),
4872
body: self.lower_block(&function.body),
4973
};
5074
self.ast.function_declarations.insert(function_declaration)
5175
}
5276

53-
fn lower_item_function(&mut self, function: &cst::FunctionDeclaration) -> FunctionId {
54-
let id = self.lower_function(function);
77+
fn lower_item_function(
78+
&mut self,
79+
function: &cst::FunctionDeclaration,
80+
annotations: Vec<AnnotationId>,
81+
) -> FunctionId {
82+
let id = self.lower_function(function, annotations);
5583
self.ast.item_functions.push(id);
5684
id
5785
}
@@ -286,7 +314,11 @@ impl<'ctx> AstGen<'ctx> {
286314
}
287315

288316
/// Lower a [`cst::TraitDeclaration`] into a [`Trait`], producing a unique [`TraitId`].
289-
fn lower_trait_declaration(&mut self, trait_declaration: &cst::TraitDeclaration) -> TraitId {
317+
fn lower_trait_declaration(
318+
&mut self,
319+
trait_declaration: &cst::TraitDeclaration,
320+
annotations: Vec<AnnotationId>,
321+
) -> TraitId {
290322
let methods = trait_declaration
291323
.methods
292324
.iter()
@@ -298,23 +330,33 @@ impl<'ctx> AstGen<'ctx> {
298330
})
299331
.collect();
300332
self.ast.traits.insert(Trait {
333+
annotations,
301334
name: self.ctx.strings.intern(&trait_declaration.name.0),
302335
methods,
303336
})
304337
}
305338

306-
fn lower_trait_implementation(&mut self, trait_implementation: &cst::TraitImplementation) {
339+
fn lower_trait_implementation(
340+
&mut self,
341+
trait_implementation: &cst::TraitImplementation,
342+
annotations: Vec<AnnotationId>,
343+
) {
307344
let methods = trait_implementation
308345
.methods
309346
.iter()
310347
.map(|method| {
311-
let method = self.lower_function(method);
348+
let method = self.lower_function(
349+
method,
350+
// Currently, annotations cannot be attached to non-item functions.
351+
Vec::new(),
352+
);
312353

313354
(self.ast[method].signature.name, method)
314355
})
315356
.collect();
316357
let target_ty = self.lower_type(&trait_implementation.ty);
317358
self.ast.trait_implementations.push(TraitImplementation {
359+
annotations,
318360
trait_name: self.ctx.strings.intern(&trait_implementation.name.0),
319361
target_ty,
320362
methods,
@@ -389,7 +431,7 @@ mod test {
389431
)]
390432
fn trait_declaration(#[case] name: &str, mut ctx: Ctx, #[case] source: &'static str) {
391433
let mut pass = AstGen::new(&mut ctx);
392-
let trait_id = pass.lower_trait_declaration(&parse(source));
434+
let trait_id = pass.lower_trait_declaration(&parse(source), Vec::new());
393435
assert_debug_snapshot!(name, pass.ast[trait_id], source);
394436
}
395437

@@ -405,7 +447,16 @@ mod test {
405447
)]
406448
fn trait_implementation(#[case] name: &str, mut ctx: Ctx, #[case] source: &'static str) {
407449
let mut pass = AstGen::new(&mut ctx);
408-
pass.lower_trait_implementation(&parse(source));
450+
pass.lower_trait_implementation(&parse(source), Vec::new());
409451
assert_debug_snapshot!(name, pass.ast.trait_implementations[0], source);
410452
}
453+
454+
#[rstest]
455+
#[case("annotation_key", "@some_annotation")]
456+
#[case("annotation_key_value", "@some_annotation(value)")]
457+
fn lower_annotation(#[case] name: &str, mut ctx: Ctx, #[case] source: &str) {
458+
let mut pass = AstGen::new(&mut ctx);
459+
let annotation = pass.lower_annotation(&parse(source));
460+
assert_debug_snapshot!(name, pass.ast.annotations[annotation], source);
461+
}
411462
}

0 commit comments

Comments
 (0)