From 31bcbedc30f00619dd8f0f8d48aeecaad70eadee Mon Sep 17 00:00:00 2001 From: Reese Williams Date: Sun, 7 Jan 2024 22:28:54 +0000 Subject: [PATCH] Implement aryptn --- fixtures/small/aryptn_actual.rb | 26 +++++++++ fixtures/small/aryptn_expected.rb | 26 +++++++++ librubyfmt/rubyfmt_lib.rb | 9 +++ librubyfmt/src/format.rs | 90 ++++++++++++++++++++++++++++- librubyfmt/src/parser_state.rs | 7 +++ librubyfmt/src/ripper_tree_types.rs | 62 +++++++++++++++++--- 6 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 fixtures/small/aryptn_actual.rb create mode 100644 fixtures/small/aryptn_expected.rb diff --git a/fixtures/small/aryptn_actual.rb b/fixtures/small/aryptn_actual.rb new file mode 100644 index 00000000..848d6550 --- /dev/null +++ b/fixtures/small/aryptn_actual.rb @@ -0,0 +1,26 @@ +case ["I will arise", "and go now", "and go to Innisfree"] +in String, String + "and a small cabin build there, of clay and wattles made" +in SmallCabin["clay", "wattles"] + "Nine bean-rows will I have there, a hive for the honey-bee," +in BeeLoudGlade[String, *, String] + "And live alone in the bee-loud glade" +end + +case ["And I shall have some peace there", "for peace", "comes dropping slow"] +in String,; + "Dropping from the veils of the morning to where the cricket sings;" +in Midnight[*] + "There midnight's all a glimmer, and noon a purple glow," +in 1, *, 2 + "And evening full of the linnet's wings." +end + +case [] +in [*] + 0 +in [] + 1 +else + 2 +end diff --git a/fixtures/small/aryptn_expected.rb b/fixtures/small/aryptn_expected.rb new file mode 100644 index 00000000..85e83c60 --- /dev/null +++ b/fixtures/small/aryptn_expected.rb @@ -0,0 +1,26 @@ +case ["I will arise", "and go now", "and go to Innisfree"] +in [String, String] + "and a small cabin build there, of clay and wattles made" +in SmallCabin["clay", "wattles"] + "Nine bean-rows will I have there, a hive for the honey-bee," +in BeeLoudGlade[String, *, String] + "And live alone in the bee-loud glade" +end + +case ["And I shall have some peace there", "for peace", "comes dropping slow"] +in [String, *] + "Dropping from the veils of the morning to where the cricket sings;" +in Midnight[*] + "There midnight's all a glimmer, and noon a purple glow," +in [1, *, 2] + "And evening full of the linnet's wings." +end + +case [] +in [*] + 0 +in [] + 1 +else + 2 +end diff --git a/librubyfmt/rubyfmt_lib.rb b/librubyfmt/rubyfmt_lib.rb index 62c8cae5..3d385c84 100644 --- a/librubyfmt/rubyfmt_lib.rb +++ b/librubyfmt/rubyfmt_lib.rb @@ -44,6 +44,7 @@ def initialize(file_data) "next" => [], "return" => [], "when" => [], + "in" => [], "case" => [], "yield" => [], "break" => [], @@ -374,6 +375,10 @@ def on_when(cond, body, tail) [:when, cond, body, tail, start_end_for_keyword('when')] end + def on_in(cond, body, tail) + [:in, cond, body, tail, start_end_for_keyword('in')] + end + def on_case(cond, body) [:case, cond, body, start_end_for_keyword('case')] end @@ -386,6 +391,10 @@ def on_break(arg) [:break, arg, start_end_for_keyword('break')] end + def on_var_field(*args) + with_lineno { super } + end + def on_tlambda(*args) @tlambda_stack << lineno super diff --git a/librubyfmt/src/format.rs b/librubyfmt/src/format.rs index 8e3c45e3..a8fd91d6 100644 --- a/librubyfmt/src/format.rs +++ b/librubyfmt/src/format.rs @@ -407,7 +407,10 @@ pub fn format_mlhs(ps: &mut dyn ConcreteParserState, mlhs: MLhs) { } fn bind_var_field(ps: &mut dyn ConcreteParserState, vf: &VarField) { - ps.bind_variable((vf.1).clone().to_local_string()) + ps.bind_variable((vf.1).clone().expect( + "Var ref fields are only nilable in pattern matching, and we don't do var binding there", + ) + .to_local_string()) } fn bind_ident(ps: &mut dyn ConcreteParserState, id: &Ident) { @@ -1732,7 +1735,12 @@ pub fn format_top_const_field(ps: &mut dyn ConcreteParserState, tcf: TopConstFie pub fn format_var_field(ps: &mut dyn ConcreteParserState, vf: VarField) { let left = vf.1; - format_var_ref_type(ps, left); + if let Some(var_ref_type) = left { + format_var_ref_type(ps, var_ref_type); + } else { + // Nil var fields are used for "*" matchers in patterns + ps.emit_ident("*".to_string()); + } } pub fn format_aref_field(ps: &mut dyn ConcreteParserState, af: ArefField) { @@ -3337,7 +3345,10 @@ pub fn format_case(ps: &mut dyn ConcreteParserState, case: Case) { ps.with_start_of_line( true, Box::new(|ps| { - format_when_or_else(ps, WhenOrElse::When(tail)); + match tail { + WhenOrIn::When(when) => format_when_or_else(ps, WhenOrElse::When(when)), + WhenOrIn::In(in_node) => format_in_or_else(ps, InOrElse::In(in_node)), + } ps.emit_end(); }), ); @@ -3349,6 +3360,78 @@ pub fn format_case(ps: &mut dyn ConcreteParserState, case: Case) { ps.on_line(case.3 .1); } +fn format_in_or_else(ps: &mut dyn ConcreteParserState, in_or_else: InOrElse) { + match in_or_else { + InOrElse::In(in_node) => { + let In(_, pattern, body, next_in_or_else, start_end) = in_node; + ps.on_line(start_end.start_line()); + ps.emit_indent(); + ps.emit_in_keyword(); + ps.emit_space(); + + ps.with_start_of_line( + false, + Box::new(|ps| { + format_pattern(ps, pattern); + }), + ); + + ps.new_block(Box::new(|ps| { + ps.with_start_of_line( + true, + Box::new(|ps| { + ps.emit_newline(); + for expr in body { + format_expression(ps, expr); + } + }), + ); + })); + + if let Some(in_or_else) = next_in_or_else { + format_in_or_else(ps, *in_or_else); + } + } + InOrElse::Else(else_node) => { + // `else` blocks are the same for `when` and `in` case statements + format_when_or_else(ps, WhenOrElse::Else(else_node)); + } + } +} + +fn format_pattern(ps: &mut dyn ConcreteParserState, pattern_node: PatternNode) { + match pattern_node { + PatternNode::Aryptn(aryptn) => format_aryptn(ps, aryptn), + } +} + +fn format_aryptn(ps: &mut dyn ConcreteParserState, aryptn: Aryptn) { + let Aryptn(_, maybe_collection_name, maybe_pre_star_list, maybe_star, maybe_post_star_list) = + aryptn; + if let Some(collection_name) = maybe_collection_name { + format_var_ref(ps, collection_name); + } + ps.breakable_of( + BreakableDelims::for_array(), + Box::new(|ps| { + let mut vals = Vec::new(); + if let Some(pre_star_list) = maybe_pre_star_list { + vals.append(&mut pre_star_list.clone()); + } + if let Some(star) = maybe_star { + vals.push(Expression::Ident(Ident::new( + "*".to_string(), + LineCol(star.2.start_line(), 0), + ))); + } + if let Some(post_star_list) = maybe_post_star_list { + vals.append(&mut post_star_list.clone()); + } + format_list_like_thing_items(ps, vals, None, false); + }), + ); +} + pub fn format_retry(ps: &mut dyn ConcreteParserState, r: Retry) { format_keyword( ps, @@ -3772,6 +3855,7 @@ pub fn format_expression(ps: &mut dyn ConcreteParserState, expression: Expressio Expression::IfMod(wm) => format_multilinable_mod(ps, wm.1, wm.2, "if".to_string()), Expression::UnlessMod(um) => format_multilinable_mod(ps, um.1, um.2, "unless".to_string()), Expression::Case(c) => format_case(ps, c), + Expression::Aryptn(arrayptn) => format_aryptn(ps, arrayptn), Expression::Retry(r) => format_retry(ps, r), Expression::Redo(r) => format_redo(ps, r), Expression::SClass(sc) => format_sclass(ps, sc), diff --git a/librubyfmt/src/parser_state.rs b/librubyfmt/src/parser_state.rs index e160edc4..91950582 100644 --- a/librubyfmt/src/parser_state.rs +++ b/librubyfmt/src/parser_state.rs @@ -73,6 +73,7 @@ where fn emit_class_keyword(&mut self); fn emit_do_keyword(&mut self); fn emit_when_keyword(&mut self); + fn emit_in_keyword(&mut self); fn emit_case_keyword(&mut self); fn emit_rescue(&mut self); fn emit_open_square_bracket(&mut self); @@ -710,6 +711,12 @@ impl ConcreteParserState for BaseParserState { }); } + fn emit_in_keyword(&mut self) { + self.push_concrete_token(ConcreteLineToken::Keyword { + keyword: "in".to_string(), + }); + } + fn emit_do_keyword(&mut self) { self.push_concrete_token(ConcreteLineToken::DoKeyword); } diff --git a/librubyfmt/src/ripper_tree_types.rs b/librubyfmt/src/ripper_tree_types.rs index a24e0fbe..301dbab3 100644 --- a/librubyfmt/src/ripper_tree_types.rs +++ b/librubyfmt/src/ripper_tree_types.rs @@ -143,6 +143,7 @@ pub enum Expression { IfMod(IfMod), UnlessMod(UnlessMod), Case(Case), + Aryptn(Aryptn), Retry(Retry), Redo(Redo), SClass(SClass), @@ -266,6 +267,18 @@ impl Expression { | Expression::EndBlock(EndBlock(_, exprs)) => { exprs.first().and_then(|expr| expr.start_line()) } + Expression::Aryptn(Aryptn(_, _, exprs, star, ..)) => exprs + .as_ref() + .map(|exprs| exprs.first().map(|expr| expr.start_line()).flatten()) + .flatten() + .or_else(|| { + Some( + star.as_ref() + .expect("If first exprs list is empty, there must be a * pattern") + .2 + .start_line(), + ) + }), // Pick the first of either expression, since these can be e.g. `foo..bar` or `foo..` or `..bar` Expression::Dot2(Dot2(_, maybe_first_expr, maybe_second_expr)) | Expression::Dot3(Dot3(_, maybe_first_expr, maybe_second_expr)) => maybe_first_expr @@ -316,7 +329,7 @@ pub enum MLhsInner { impl MLhsInner { pub fn start_line(&self) -> Option { match self { - MLhsInner::VarField(VarField(_, var_ref_type)) => Some(var_ref_type.start_line()), + MLhsInner::VarField(VarField(.., start_end)) => Some(start_end.start_line()), MLhsInner::Field(Field(_, expr, ..)) => expr.start_line(), MLhsInner::RestParam(RestParam(.., rest_param_assignable)) => rest_param_assignable .as_ref() @@ -599,9 +612,7 @@ impl RestParamAssignable { match self { RestParamAssignable::ArefField(ArefField(.., linecol)) | RestParamAssignable::Ident(Ident(.., linecol)) => Some(linecol.0), - RestParamAssignable::VarField(VarField(.., var_ref_type)) => { - Some(var_ref_type.start_line()) - } + RestParamAssignable::VarField(VarField(.., start_end)) => Some(start_end.start_line()), } } } @@ -622,7 +633,7 @@ pub enum Assignable { impl Assignable { pub fn start_line(&self) -> Option { match self { - Assignable::VarField(VarField(.., var_ref_type)) => Some(var_ref_type.start_line()), + Assignable::VarField(VarField(.., start_end)) => Some(start_end.start_line()), Assignable::RestParam(RestParam(.., rest_param_assignable)) => rest_param_assignable .as_ref() .and_then(|rpa| rpa.start_line()), @@ -661,7 +672,7 @@ pub struct ConstPathField(pub const_path_field_tag, pub Box, pub Con def_tag!(var_field_tag, "var_field"); #[derive(Deserialize, Debug, Clone)] -pub struct VarField(pub var_field_tag, pub VarRefType); +pub struct VarField(pub var_field_tag, pub Option, pub StartEnd); def_tag!(field_tag, "field"); #[derive(Deserialize, Debug, Clone)] @@ -2340,7 +2351,7 @@ def_tag!(case_tag, "case"); pub struct Case( case_tag, pub Option>, - pub When, + pub WhenOrIn, pub StartEnd, ); @@ -2354,12 +2365,49 @@ pub struct When( pub StartEnd, ); +#[derive(RipperDeserialize, Debug, Clone)] +pub enum WhenOrIn { + When(When), + In(In), +} + +def_tag!(in_tag, "in"); +#[derive(Deserialize, Debug, Clone)] +pub struct In( + pub in_tag, + pub PatternNode, // current pattern + pub Vec, // body + pub Option>, // next in/else + pub StartEnd, +); + +#[derive(RipperDeserialize, Debug, Clone)] +pub enum PatternNode { + Aryptn(Aryptn), +} + +def_tag!(arrayptn_tag, "aryptn"); +#[derive(Deserialize, Debug, Clone)] +pub struct Aryptn( + pub arrayptn_tag, + pub Option, // Container type, e.g. `in Foo["a", "b"]` + pub Option>, // list of values before the first * + pub Option, // "*" pattern + pub Option>, // list of values the first * +); + #[derive(RipperDeserialize, Debug, Clone)] pub enum WhenOrElse { When(When), Else(CaseElse), } +#[derive(RipperDeserialize, Debug, Clone)] +pub enum InOrElse { + In(In), + Else(CaseElse), +} + def_tag!(case_else_tag, "else"); #[derive(Deserialize, Debug, Clone)] pub struct CaseElse(case_else_tag, pub Vec, pub StartEnd);