Skip to content

Commit 7dc4b78

Browse files
Support parsing Luau function attributes (#337)
Supports the parsing of Luau function attributes, such as `@native` or `@checked`. Does not perform any validation on the name of attributes, unlike Luau. Unfortunately, due to the existing `Attribute` struct for Lua 5.4 `<const>` attributes, we have to name the struct `LuauAttribute`. **[BREAKING CHANGE]**: Because attributes can be attached to anonymous functions, we create a new `AnonymousFunction` struct to handle this. This replaces the old `Box<(TokenReference, FunctionBody)>`. Closes #336
1 parent 87d68b2 commit 7dc4b78

File tree

27 files changed

+2514
-87
lines changed

27 files changed

+2514
-87
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- In Unpacking e.g. `local a, b, c = t` is equivalent to `local a, b, c = t.a, t.b, t.c`
1414
- Set Constructors e.g. `t = { .x, .y }` is equivalent to `t = { x = true, y = true }`
1515
- C-Style Comments (single & multiline) e.g. `/* comment */`
16-
- Compile Time Jenkins' Hashes e.g. ``` `Hello, World!` -> 1395890823```
16+
- Compile Time Jenkins' Hashes e.g. ``` `Hello, World!` -> 1395890823```
17+
- Luau: support parsing function attributes (rfc: https://github.com/luau-lang/rfcs/blob/master/docs/syntax-attributes-functions.md)
18+
19+
### Changed
20+
- **[BREAKING CHANGE]** Introduced a new struct `AnonymousFunction` that is used in the `Expression::Function` variant. The old `Box<(TokenReference, FunctionBody)>` is now replaced with `Box<AnonymousFunction>`, and allows us to attach more data to an anonymous function (at the moment, mainly Luau attributes)
1721

1822
## [1.2.0] - 2025-01-09
1923

full-moon/src/ast/luau.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,3 +1205,42 @@ impl<'a> Iterator for ExpressionsIterator<'a> {
12051205
Some(&segment.expression)
12061206
}
12071207
}
1208+
1209+
/// An attribute, such as `@native`
1210+
#[derive(Clone, Debug, Display, PartialEq, Node, Visit)]
1211+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1212+
#[display("{at_sign}{name}")]
1213+
pub struct LuauAttribute {
1214+
pub(crate) at_sign: TokenReference,
1215+
pub(crate) name: TokenReference,
1216+
}
1217+
1218+
impl LuauAttribute {
1219+
/// Creates a new ElseIf from the given condition
1220+
pub fn new(name: TokenReference) -> Self {
1221+
Self {
1222+
at_sign: TokenReference::symbol("@").unwrap(),
1223+
name,
1224+
}
1225+
}
1226+
1227+
/// The `@` token
1228+
pub fn at_sign(&self) -> &TokenReference {
1229+
&self.at_sign
1230+
}
1231+
1232+
/// The name of the attribute, `native` in `@native`
1233+
pub fn name(&self) -> &TokenReference {
1234+
&self.name
1235+
}
1236+
1237+
/// Returns a new Attribute with the given `@` token
1238+
pub fn with_at_sign(self, at_sign: TokenReference) -> Self {
1239+
Self { at_sign, ..self }
1240+
}
1241+
1242+
/// Returns a new Attribute with the given name
1243+
pub fn with_name(self, name: TokenReference) -> Self {
1244+
Self { name, ..self }
1245+
}
1246+
}

full-moon/src/ast/mod.rs

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,74 @@ impl Default for TableConstructor {
279279
}
280280
}
281281

282+
/// An anonymous function, such as `function() end`
283+
#[derive(Clone, Debug, Display, PartialEq, Node, Visit)]
284+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
285+
#[cfg_attr(not(feature = "luau"), display("{function_token}{body}"))]
286+
#[cfg_attr(
287+
feature = "luau",
288+
display("{}{}{}", join_vec(attributes), function_token, body)
289+
)]
290+
pub struct AnonymousFunction {
291+
#[cfg(feature = "luau")]
292+
attributes: Vec<LuauAttribute>,
293+
function_token: TokenReference,
294+
body: FunctionBody,
295+
}
296+
297+
impl AnonymousFunction {
298+
/// Returns a new AnonymousFunction
299+
pub fn new() -> Self {
300+
AnonymousFunction {
301+
#[cfg(feature = "luau")]
302+
attributes: Vec::new(),
303+
function_token: TokenReference::basic_symbol("function"),
304+
body: FunctionBody::new(),
305+
}
306+
}
307+
308+
/// The attributes in the function, e.g. `@native`
309+
#[cfg(feature = "luau")]
310+
pub fn attributes(&self) -> impl Iterator<Item = &LuauAttribute> {
311+
self.attributes.iter()
312+
}
313+
314+
/// The `function` token
315+
pub fn function_token(&self) -> &TokenReference {
316+
&self.function_token
317+
}
318+
319+
/// The function body, everything except `function` in `function(a, b, c) call() end`
320+
pub fn body(&self) -> &FunctionBody {
321+
&self.body
322+
}
323+
324+
/// Returns a new AnonymousFunction with the given attributes (e.g. `@native`)
325+
#[cfg(feature = "luau")]
326+
pub fn with_attributes(self, attributes: Vec<LuauAttribute>) -> Self {
327+
Self { attributes, ..self }
328+
}
329+
330+
/// Returns a new AnonymousFunction with the given `function` token
331+
pub fn with_function_token(self, function_token: TokenReference) -> Self {
332+
Self {
333+
function_token,
334+
..self
335+
}
336+
}
337+
338+
/// Returns a new AnonymousFunction with the given function body
339+
pub fn with_body(self, body: FunctionBody) -> Self {
340+
Self { body, ..self }
341+
}
342+
}
343+
344+
impl Default for AnonymousFunction {
345+
fn default() -> Self {
346+
Self::new()
347+
}
348+
}
349+
282350
/// An expression, mostly useful for getting values
283351
#[derive(Clone, Debug, Display, PartialEq, Node)]
284352
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
@@ -315,8 +383,8 @@ pub enum Expression {
315383
},
316384

317385
/// An anonymous function, such as `function() end`
318-
#[display("{}{}", _0.0, _0.1)]
319-
Function(Box<(TokenReference, FunctionBody)>),
386+
#[display("{_0}")]
387+
Function(Box<AnonymousFunction>),
320388

321389
/// A call of a function, such as `call()`
322390
#[display("{_0}")]
@@ -1620,8 +1688,20 @@ impl Assignment {
16201688
not(feature = "luau"),
16211689
display("{local_token}{function_token}{name}{body}")
16221690
)]
1623-
#[cfg_attr(feature = "luau", display("{local_token}{function_token}{name}{body}"))]
1691+
#[cfg_attr(
1692+
feature = "luau",
1693+
display(
1694+
"{}{}{}{}{}",
1695+
join_vec(attributes),
1696+
local_token,
1697+
function_token,
1698+
name,
1699+
body
1700+
)
1701+
)]
16241702
pub struct LocalFunction {
1703+
#[cfg(feature = "luau")]
1704+
attributes: Vec<LuauAttribute>,
16251705
local_token: TokenReference,
16261706
function_token: TokenReference,
16271707
name: TokenReference,
@@ -1632,13 +1712,21 @@ impl LocalFunction {
16321712
/// Returns a new LocalFunction from the given name
16331713
pub fn new(name: TokenReference) -> Self {
16341714
LocalFunction {
1715+
#[cfg(feature = "luau")]
1716+
attributes: Vec::new(),
16351717
local_token: TokenReference::basic_symbol("local "),
16361718
function_token: TokenReference::basic_symbol("function "),
16371719
name,
16381720
body: FunctionBody::new(),
16391721
}
16401722
}
16411723

1724+
/// The attributes in the function, e.g. `@native`
1725+
#[cfg(feature = "luau")]
1726+
pub fn attributes(&self) -> impl Iterator<Item = &LuauAttribute> {
1727+
self.attributes.iter()
1728+
}
1729+
16421730
/// The `local` token
16431731
pub fn local_token(&self) -> &TokenReference {
16441732
&self.local_token
@@ -1659,6 +1747,12 @@ impl LocalFunction {
16591747
&self.name
16601748
}
16611749

1750+
/// Returns a new LocalFunction with the given attributes (e.g. `@native`)
1751+
#[cfg(feature = "luau")]
1752+
pub fn with_attributes(self, attributes: Vec<LuauAttribute>) -> Self {
1753+
Self { attributes, ..self }
1754+
}
1755+
16621756
/// Returns a new LocalFunction with the given `local` token
16631757
pub fn with_local_token(self, local_token: TokenReference) -> Self {
16641758
Self {
@@ -1992,8 +2086,13 @@ impl FunctionName {
19922086
#[derive(Clone, Debug, Display, PartialEq, Node, Visit)]
19932087
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
19942088
#[cfg_attr(not(feature = "luau"), display("{function_token}{name}{body}"))]
1995-
#[cfg_attr(feature = "luau", display("{function_token}{name}{body}"))]
2089+
#[cfg_attr(
2090+
feature = "luau",
2091+
display("{}{}{}{}", join_vec(attributes), function_token, name, body)
2092+
)]
19962093
pub struct FunctionDeclaration {
2094+
#[cfg(feature = "luau")]
2095+
attributes: Vec<LuauAttribute>,
19972096
function_token: TokenReference,
19982097
name: FunctionName,
19992098
body: FunctionBody,
@@ -2003,12 +2102,20 @@ impl FunctionDeclaration {
20032102
/// Creates a new FunctionDeclaration from the given name
20042103
pub fn new(name: FunctionName) -> Self {
20052104
Self {
2105+
#[cfg(feature = "luau")]
2106+
attributes: Vec::new(),
20062107
function_token: TokenReference::basic_symbol("function "),
20072108
name,
20082109
body: FunctionBody::new(),
20092110
}
20102111
}
20112112

2113+
/// The attributes in the function, e.g. `@native`
2114+
#[cfg(feature = "luau")]
2115+
pub fn attributes(&self) -> impl Iterator<Item = &LuauAttribute> {
2116+
self.attributes.iter()
2117+
}
2118+
20122119
/// The `function` token
20132120
pub fn function_token(&self) -> &TokenReference {
20142121
&self.function_token
@@ -2024,6 +2131,12 @@ impl FunctionDeclaration {
20242131
&self.name
20252132
}
20262133

2134+
/// Returns a new FunctionDeclaration with the given attributes (e.g. `@native`)
2135+
#[cfg(feature = "luau")]
2136+
pub fn with_attributes(self, attributes: Vec<LuauAttribute>) -> Self {
2137+
Self { attributes, ..self }
2138+
}
2139+
20272140
/// Returns a new FunctionDeclaration with the given `function` token
20282141
pub fn with_function_token(self, function_token: TokenReference) -> Self {
20292142
Self {

0 commit comments

Comments
 (0)