diff --git a/grammar.ebnf b/grammar.ebnf index 3f061d6..33b1059 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -8,9 +8,13 @@ Visibility = "pub"? ; File = Item* EOI ; -Item = Visibility ItemKind ; +Attrs = ('#' '[' (Meta ',') Meta? ']')* ; +Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ; + + +Item = Attrs* Visibility ItemKind ; ItemKind = Const | Static | Module - | Function | Struct | Enum + | Function | Struct | Enum | Alias | Impl ; diff --git a/libconlang/src/ast.rs b/libconlang/src/ast.rs index 3e14fa5..1e74d81 100644 --- a/libconlang/src/ast.rs +++ b/libconlang/src/ast.rs @@ -32,11 +32,31 @@ pub struct File { pub items: Vec, } +// Metadata decorators +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Attrs { + pub meta: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Meta { + pub name: Identifier, + pub kind: MetaKind, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MetaKind { + Plain, + Equals(Literal), + Func(Vec), +} + // Items /// Stores an [ItemKind] and associated metadata #[derive(Clone, Debug, PartialEq, Eq)] pub struct Item { pub extents: Span, + pub attrs: Attrs, pub vis: Visibility, pub kind: ItemKind, } diff --git a/libconlang/src/ast/ast_impl.rs b/libconlang/src/ast/ast_impl.rs index db255eb..b71de7a 100644 --- a/libconlang/src/ast/ast_impl.rs +++ b/libconlang/src/ast/ast_impl.rs @@ -77,10 +77,39 @@ mod display { } } + impl Display for Attrs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { meta } = self; + if meta.is_empty() { + return Ok(()); + } + "#".fmt(f)?; + delimit(separate(meta, ", "), INLINE_SQUARE)(f)?; + "\n".fmt(f) + } + } + impl Display for Meta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { name, kind } = self; + write!(f, "{name}{kind}") + } + } + impl Display for MetaKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MetaKind::Plain => Ok(()), + MetaKind::Equals(v) => write!(f, " = {v}"), + MetaKind::Func(args) => delimit(separate(args, ", "), INLINE_PARENS)(f), + } + } + } + impl Display for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.vis.fmt(f)?; - match &self.kind { + let Self { extents: _, attrs, vis, kind } = self; + attrs.fmt(f)?; + vis.fmt(f)?; + match kind { ItemKind::Alias(v) => v.fmt(f), ItemKind::Const(v) => v.fmt(f), ItemKind::Static(v) => v.fmt(f), diff --git a/libconlang/src/parser.rs b/libconlang/src/parser.rs index 6f1b3ba..974139d 100644 --- a/libconlang/src/parser.rs +++ b/libconlang/src/parser.rs @@ -68,6 +68,9 @@ pub mod error { pub enum Parsing { File, + Attrs, + Meta, + Item, Visibility, Mutability, @@ -161,6 +164,10 @@ pub mod error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Parsing::File => "a file", + + Parsing::Attrs => "an attribute-set", + Parsing::Meta => "an attribute", + Parsing::Item => "an item", Parsing::Visibility => "a visibility qualifier", Parsing::Mutability => "a mutability qualifier", @@ -371,7 +378,8 @@ fn rep<'t, T>( /// Expands to a pattern which matches item-like [Token] [Type]s macro item_like() { - Type::Keyword( + Type::Hash + | Type::Keyword( Keyword::Pub | Keyword::Type | Keyword::Const @@ -406,6 +414,7 @@ impl<'t> Parser<'t> { let start = self.loc(); Ok(Item { vis: self.visibility()?, + attrs: self.attributes()?, kind: self.itemkind()?, extents: Span(start, self.loc()), }) @@ -464,6 +473,41 @@ impl<'t> Parser<'t> { } } +/// Attribute parsing +impl<'t> Parser<'t> { + /// Parses an [attribute set](Attrs) + pub fn attributes(&mut self) -> PResult { + if self.match_type(Type::Hash, Parsing::Attrs).is_err() { + return Ok(Attrs { meta: vec![] }); + } + let meta = delim( + sep(Self::meta, Type::Comma, BRACKETS.1, Parsing::Attrs), + BRACKETS, + Parsing::Attrs, + ); + Ok(Attrs { meta: meta(self)? }) + } + pub fn meta(&mut self) -> PResult { + Ok(Meta { name: self.identifier()?, kind: self.meta_kind()? }) + } + pub fn meta_kind(&mut self) -> PResult { + const PARSING: Parsing = Parsing::Meta; + let lit_tuple = delim( + sep(Self::literal, Type::Comma, PARENS.1, PARSING), + PARENS, + PARSING, + ); + Ok(match self.peek_type(PARSING) { + Ok(Type::Eq) => { + self.consume_peeked(); + MetaKind::Equals(self.literal()?) + } + Ok(Type::LParen) => MetaKind::Func(lit_tuple(self)?), + _ => MetaKind::Plain, + }) + } +} + /// Item parsing impl<'t> Parser<'t> { /// Parses an [ItemKind]