diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dc1a1b4..4c7e9fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Added support for parsing Luau's floor division `//` + ### Changed - Flattened `Expression::Value` to all be variants of `Expression` directly, as this was not used anywhere else. The extra `type_assertion` field has been moved into a new variant `Expression::TypeAssertion`. None of these variants are boxed. - The following fields/variants have been changed from `Expression` to `Box<Expression>`: `Prefix::Expression`, `Var::Expression`, `IfExpression::condition`, `IfExpression::if_expression`, `IfExpression::else_expression`. diff --git a/full-moon/src/ast/mod.rs b/full-moon/src/ast/mod.rs index 978b0401..151284ac 100644 --- a/full-moon/src/ast/mod.rs +++ b/full-moon/src/ast/mod.rs @@ -2028,7 +2028,7 @@ make_op!(BinOp, TwoEqual, #[cfg(feature = "lua53")] Ampersand, - #[cfg(feature = "lua53")] + #[cfg(any(feature = "roblox", feature = "lua53"))] DoubleSlash, #[cfg(feature = "lua53")] DoubleLessThan, @@ -2049,6 +2049,8 @@ impl BinOp { match *self { BinOp::Caret(_) => 8, BinOp::Star(_) | BinOp::Slash(_) | BinOp::Percent(_) => 6, + #[cfg(feature = "roblox")] + BinOp::DoubleSlash(_) => 6, BinOp::Plus(_) | BinOp::Minus(_) => 5, BinOp::TwoDots(_) => 4, BinOp::GreaterThan(_) @@ -2110,9 +2112,10 @@ impl BinOp { | BinOp::TildeEqual(token) | BinOp::TwoDots(token) | BinOp::TwoEqual(token) => token, + #[cfg(any(feature = "roblox", feature = "lua53"))] + BinOp::DoubleSlash(token) => token, #[cfg(feature = "lua53")] BinOp::Ampersand(token) - | BinOp::DoubleSlash(token) | BinOp::DoubleLessThan(token) | BinOp::Pipe(token) | BinOp::DoubleGreaterThan(token) diff --git a/full-moon/src/ast/parsers.rs b/full-moon/src/ast/parsers.rs index 24a23720..c25e38e2 100644 --- a/full-moon/src/ast/parsers.rs +++ b/full-moon/src/ast/parsers.rs @@ -2221,12 +2221,16 @@ define_parser!(ParseBinOp, BinOp, |_, state| { } else if let Ok((state, operator)) = ParseSymbol(Symbol::TwoEqual).parse(state) { Ok((state, BinOp::TwoEqual(operator))) } else { + // Luau & Lua 5.3 + #[cfg(any(feature = "roblox", feature = "lua53"))] + if let Ok((state, operator)) = ParseSymbol(Symbol::DoubleSlash).parse(state) { + return Ok((state, BinOp::DoubleSlash(operator))); + } + // Lua 5.3 #[cfg(feature = "lua53")] if let Ok((state, operator)) = ParseSymbol(Symbol::Ampersand).parse(state) { return Ok((state, BinOp::Ampersand(operator))); - } else if let Ok((state, operator)) = ParseSymbol(Symbol::DoubleSlash).parse(state) { - return Ok((state, BinOp::DoubleSlash(operator))); } else if let Ok((state, operator)) = ParseSymbol(Symbol::DoubleLessThan).parse(state) { return Ok((state, BinOp::DoubleLessThan(operator))); } else if let Ok((state, operator)) = ParseSymbol(Symbol::Pipe).parse(state) { diff --git a/full-moon/src/atom.rs b/full-moon/src/atom.rs index e2fff7b6..fdc168b6 100644 --- a/full-moon/src/atom.rs +++ b/full-moon/src/atom.rs @@ -356,7 +356,7 @@ pub(crate) enum Atom { #[token("/")] Slash, - #[cfg(feature = "lua53")] + #[cfg(any(feature = "roblox", feature = "lua53"))] #[token("//")] DoubleSlash, diff --git a/full-moon/src/tokenizer.rs b/full-moon/src/tokenizer.rs index 1664a9a6..55284aac 100644 --- a/full-moon/src/tokenizer.rs +++ b/full-moon/src/tokenizer.rs @@ -172,7 +172,7 @@ pub enum Symbol { #[cfg_attr(feature = "serde", serde(rename = "/"))] Slash, - #[cfg(feature = "lua53")] + #[cfg(any(feature = "roblox", feature = "lua53"))] #[cfg_attr(feature = "serde", serde(rename = "//"))] DoubleSlash, @@ -265,7 +265,7 @@ impl TryFrom<Atom> for Symbol { Atom::RightParen => Symbol::RightParen, Atom::Semicolon => Symbol::Semicolon, Atom::Slash => Symbol::Slash, - #[cfg(feature = "lua53")] + #[cfg(any(feature = "roblox", feature = "lua53"))] Atom::DoubleSlash => Symbol::DoubleSlash, Atom::Star => Symbol::Star, #[cfg(feature = "lua53")] @@ -349,7 +349,7 @@ impl Display for Symbol { Symbol::RightParen => ")", Symbol::Semicolon => ";", Symbol::Slash => "/", - #[cfg(feature = "lua53")] + #[cfg(any(feature = "roblox", feature = "lua53"))] Symbol::DoubleSlash => "//", Symbol::Star => "*", #[cfg(feature = "lua53")] diff --git a/full-moon/tests/roblox_cases/pass/floor_division/ast.snap b/full-moon/tests/roblox_cases/pass/floor_division/ast.snap new file mode 100644 index 00000000..31e67c65 --- /dev/null +++ b/full-moon/tests/roblox_cases/pass/floor_division/ast.snap @@ -0,0 +1,173 @@ +--- +source: full-moon/tests/pass_cases.rs +expression: ast.nodes() +--- +stmts: + - - LocalAssignment: + local_token: + leading_trivia: [] + token: + start_position: + bytes: 0 + line: 1 + character: 1 + end_position: + bytes: 5 + line: 1 + character: 6 + token_type: + type: Symbol + symbol: local + trailing_trivia: + - start_position: + bytes: 5 + line: 1 + character: 6 + end_position: + bytes: 6 + line: 1 + character: 7 + token_type: + type: Whitespace + characters: " " + name_list: + pairs: + - End: + leading_trivia: [] + token: + start_position: + bytes: 6 + line: 1 + character: 7 + end_position: + bytes: 7 + line: 1 + character: 8 + token_type: + type: Identifier + identifier: x + trailing_trivia: + - start_position: + bytes: 7 + line: 1 + character: 8 + end_position: + bytes: 8 + line: 1 + character: 9 + token_type: + type: Whitespace + characters: " " + equal_token: + leading_trivia: [] + token: + start_position: + bytes: 8 + line: 1 + character: 9 + end_position: + bytes: 9 + line: 1 + character: 10 + token_type: + type: Symbol + symbol: "=" + trailing_trivia: + - start_position: + bytes: 9 + line: 1 + character: 10 + end_position: + bytes: 10 + line: 1 + character: 11 + token_type: + type: Whitespace + characters: " " + expr_list: + pairs: + - End: + BinaryOperator: + lhs: + Number: + leading_trivia: [] + token: + start_position: + bytes: 10 + line: 1 + character: 11 + end_position: + bytes: 11 + line: 1 + character: 12 + token_type: + type: Number + text: "1" + trailing_trivia: + - start_position: + bytes: 11 + line: 1 + character: 12 + end_position: + bytes: 12 + line: 1 + character: 13 + token_type: + type: Whitespace + characters: " " + binop: + DoubleSlash: + leading_trivia: [] + token: + start_position: + bytes: 12 + line: 1 + character: 13 + end_position: + bytes: 14 + line: 1 + character: 15 + token_type: + type: Symbol + symbol: // + trailing_trivia: + - start_position: + bytes: 14 + line: 1 + character: 15 + end_position: + bytes: 15 + line: 1 + character: 16 + token_type: + type: Whitespace + characters: " " + rhs: + Number: + leading_trivia: [] + token: + start_position: + bytes: 15 + line: 1 + character: 16 + end_position: + bytes: 16 + line: 1 + character: 17 + token_type: + type: Number + text: "2" + trailing_trivia: + - start_position: + bytes: 16 + line: 1 + character: 17 + end_position: + bytes: 17 + line: 1 + character: 17 + token_type: + type: Whitespace + characters: "\n" + - ~ + diff --git a/full-moon/tests/roblox_cases/pass/floor_division/source.lua b/full-moon/tests/roblox_cases/pass/floor_division/source.lua new file mode 100644 index 00000000..8460eebb --- /dev/null +++ b/full-moon/tests/roblox_cases/pass/floor_division/source.lua @@ -0,0 +1 @@ +local x = 1 // 2 diff --git a/full-moon/tests/roblox_cases/pass/floor_division/tokens.snap b/full-moon/tests/roblox_cases/pass/floor_division/tokens.snap new file mode 100644 index 00000000..ec026300 --- /dev/null +++ b/full-moon/tests/roblox_cases/pass/floor_division/tokens.snap @@ -0,0 +1,147 @@ +--- +source: full-moon/tests/pass_cases.rs +expression: tokens +--- +- start_position: + bytes: 0 + line: 1 + character: 1 + end_position: + bytes: 5 + line: 1 + character: 6 + token_type: + type: Symbol + symbol: local +- start_position: + bytes: 5 + line: 1 + character: 6 + end_position: + bytes: 6 + line: 1 + character: 7 + token_type: + type: Whitespace + characters: " " +- start_position: + bytes: 6 + line: 1 + character: 7 + end_position: + bytes: 7 + line: 1 + character: 8 + token_type: + type: Identifier + identifier: x +- start_position: + bytes: 7 + line: 1 + character: 8 + end_position: + bytes: 8 + line: 1 + character: 9 + token_type: + type: Whitespace + characters: " " +- start_position: + bytes: 8 + line: 1 + character: 9 + end_position: + bytes: 9 + line: 1 + character: 10 + token_type: + type: Symbol + symbol: "=" +- start_position: + bytes: 9 + line: 1 + character: 10 + end_position: + bytes: 10 + line: 1 + character: 11 + token_type: + type: Whitespace + characters: " " +- start_position: + bytes: 10 + line: 1 + character: 11 + end_position: + bytes: 11 + line: 1 + character: 12 + token_type: + type: Number + text: "1" +- start_position: + bytes: 11 + line: 1 + character: 12 + end_position: + bytes: 12 + line: 1 + character: 13 + token_type: + type: Whitespace + characters: " " +- start_position: + bytes: 12 + line: 1 + character: 13 + end_position: + bytes: 14 + line: 1 + character: 15 + token_type: + type: Symbol + symbol: // +- start_position: + bytes: 14 + line: 1 + character: 15 + end_position: + bytes: 15 + line: 1 + character: 16 + token_type: + type: Whitespace + characters: " " +- start_position: + bytes: 15 + line: 1 + character: 16 + end_position: + bytes: 16 + line: 1 + character: 17 + token_type: + type: Number + text: "2" +- start_position: + bytes: 16 + line: 1 + character: 17 + end_position: + bytes: 17 + line: 1 + character: 17 + token_type: + type: Whitespace + characters: "\n" +- start_position: + bytes: 17 + line: 2 + character: 1 + end_position: + bytes: 17 + line: 2 + character: 1 + token_type: + type: Eof +