From c7067b02db9b5695becf8921a0d5da5213d1daaf Mon Sep 17 00:00:00 2001 From: Alex Snezhko Date: Fri, 10 Feb 2023 23:21:33 -0500 Subject: [PATCH 1/3] feat(compiler): Arbitrary-position spreads --- compiler/src/codegen/compcore.re | 95 ++++++++ compiler/src/codegen/garbage_collection.re | 6 +- compiler/src/codegen/mashtree.re | 1 + compiler/src/codegen/transl_anf.re | 2 + compiler/src/formatting/debug.re | 2 + compiler/src/formatting/format.re | 206 ++++++++++++++---- compiler/src/middle_end/analyze_free_vars.re | 6 + compiler/src/middle_end/analyze_purity.re | 1 + compiler/src/middle_end/analyze_tail_calls.re | 1 + compiler/src/middle_end/anf_helper.re | 8 + compiler/src/middle_end/anf_helper.rei | 9 + compiler/src/middle_end/anf_iterator.re | 1 + compiler/src/middle_end/anf_mapper.re | 4 + compiler/src/middle_end/anftree.re | 1 + compiler/src/middle_end/anftree.rei | 1 + compiler/src/middle_end/linearize.re | 14 ++ compiler/src/parsing/ast_helper.re | 122 +++++++++-- compiler/src/parsing/ast_helper.rei | 14 ++ compiler/src/parsing/ast_mapper.re | 7 + compiler/src/parsing/parser.messages | 89 ++------ compiler/src/parsing/parser.mly | 6 +- compiler/src/parsing/parsetree.re | 14 ++ compiler/src/parsing/parsetree_iter.re | 2 + compiler/src/typed/typecore.re | 21 ++ compiler/src/typed/typedtree.re | 6 + compiler/src/typed/typedtree.rei | 5 + compiler/src/typed/typedtreeIter.re | 1 + compiler/src/typed/typedtreeMap.re | 2 + compiler/test/grainfmt/spreads.expected.gr | 24 ++ compiler/test/grainfmt/spreads.input.gr | 18 ++ compiler/test/suites/arrays.re | 21 ++ compiler/test/suites/lists.re | 21 ++ stdlib/pervasives.gr | 1 + stdlib/runtime/concat.gr | 65 ++++++ 34 files changed, 668 insertions(+), 129 deletions(-) create mode 100644 stdlib/runtime/concat.gr diff --git a/compiler/src/codegen/compcore.re b/compiler/src/codegen/compcore.re index 7a4ba26cd8..0dccac74eb 100644 --- a/compiler/src/codegen/compcore.re +++ b/compiler/src/codegen/compcore.re @@ -80,6 +80,19 @@ let equal_mod = "GRAIN$MODULE$runtime/equal"; let equal_ident = Ident.create_persistent("equal"); let equal_closure_ident = Ident.create_persistent("GRAIN$EXPORT$equal"); +/* List concat */ +let collection_concat_mod = "GRAIN$MODULE$runtime/concat"; +let list_concat_ident = Ident.create_persistent("listConcat"); +let list_concat_closure_ident = + Ident.create_persistent("GRAIN$EXPORT$listConcat"); +let array_concat_ident = Ident.create_persistent("arrayConcat"); +let array_concat_closure_ident = + Ident.create_persistent("GRAIN$EXPORT$arrayConcat"); + +/* JS-runner support */ +let console_mod = "console"; +let tracepoint_ident = Ident.create_persistent("tracepoint"); + let required_global_imports = [ { mimp_id: reloc_base, @@ -174,6 +187,24 @@ let grain_runtime_imports = [ mimp_setup: MSetupNone, mimp_used: false, }, + { + mimp_id: list_concat_closure_ident, + mimp_mod: collection_concat_mod, + mimp_name: Ident.name(list_concat_closure_ident), + mimp_type: MGlobalImport(Types.Unmanaged(WasmI32), true), + mimp_kind: MImportWasm, + mimp_setup: MSetupNone, + mimp_used: false, + }, + { + mimp_id: array_concat_closure_ident, + mimp_mod: collection_concat_mod, + mimp_name: Ident.name(array_concat_closure_ident), + mimp_type: MGlobalImport(Types.Unmanaged(WasmI32), true), + mimp_kind: MImportWasm, + mimp_setup: MSetupNone, + mimp_used: false, + }, ]; let runtime_global_imports = @@ -248,6 +279,24 @@ let grain_function_imports = [ mimp_setup: MSetupNone, mimp_used: false, }, + { + mimp_id: list_concat_ident, + mimp_mod: collection_concat_mod, + mimp_name: Ident.name(list_concat_ident), + mimp_type: MFuncImport([Types.Managed, Types.Managed], [Types.Managed]), + mimp_kind: MImportWasm, + mimp_setup: MSetupNone, + mimp_used: false, + }, + { + mimp_id: array_concat_ident, + mimp_mod: collection_concat_mod, + mimp_name: Ident.name(array_concat_ident), + mimp_type: MFuncImport([Types.Managed, Types.Managed], [Types.Managed]), + mimp_kind: MImportWasm, + mimp_setup: MSetupNone, + mimp_used: false, + }, ]; let runtime_function_imports = @@ -404,6 +453,44 @@ let call_equal = (wasm_mod, env, args) => ], Type.int32, ); +let call_list_concat = (wasm_mod, env, args) => { + let args = [ + Expression.Global_get.make( + wasm_mod, + get_wasm_imported_name( + collection_concat_mod, + list_concat_closure_ident, + ), + Type.int32, + ), + ...args, + ]; + Expression.Call.make( + wasm_mod, + get_wasm_imported_name(collection_concat_mod, list_concat_ident), + args, + Type.int32, + ); +}; +let call_array_concat = (wasm_mod, env, args) => { + let args = [ + Expression.Global_get.make( + wasm_mod, + get_wasm_imported_name( + collection_concat_mod, + array_concat_closure_ident, + ), + Type.int32, + ), + ...args, + ]; + Expression.Call.make( + wasm_mod, + get_wasm_imported_name(collection_concat_mod, array_concat_ident), + args, + Type.int32, + ); +}; /** Untags the number */ @@ -2929,6 +3016,14 @@ and compile_instr = (wasm_mod, env, instr) => compile_record_op(wasm_mod, env, record, record_op) | MClosureOp(closure_op, closure) => compile_closure_op(wasm_mod, env, closure, closure_op) + | MCollectionConcat(t, colls) => + let colls_arr = allocate_array(wasm_mod, env, colls); + let concat = + switch (t) { + | TExpListConcat => call_list_concat + | TExpArrayConcat => call_array_concat + }; + concat(wasm_mod, env, [colls_arr]); | MPrim0(p0) => compile_prim0(wasm_mod, env, p0) | MPrim1(p1, arg) => compile_prim1(wasm_mod, env, p1, arg, instr.instr_loc) | MPrim2(p2, arg1, arg2) => compile_prim2(wasm_mod, env, p2, arg1, arg2) diff --git a/compiler/src/codegen/garbage_collection.re b/compiler/src/codegen/garbage_collection.re index 886bfee6d9..cbb7501d6c 100644 --- a/compiler/src/codegen/garbage_collection.re +++ b/compiler/src/codegen/garbage_collection.re @@ -36,7 +36,8 @@ let instr_produces_value = instr => | MBoxOp(_) | MArrayOp(_) | MAdtOp(_) - | MRecordOp(_) => true + | MRecordOp(_) + | MCollectionConcat(_) => true | MClosureOp(MClosureSetPtr(_), _) => false | MStore(_) => false | MSet(_) => true @@ -177,6 +178,7 @@ let rec analyze_usage = instrs => { }; process_imm(imm); | MClosureOp(closure_op, imm) => process_imm(imm) + | MCollectionConcat(t, collections) => List.iter(process_imm, collections) | MStore(binds) => List.iter( ((bind, instr)) => { @@ -502,6 +504,8 @@ let rec apply_gc = (~level, ~loop_context, ~implicit_return=false, instrs) => { MRecordOp(record_op, handle_imm(~non_gc_instr=true, imm)); | MClosureOp(closure_op, imm) => MClosureOp(closure_op, handle_imm(~non_gc_instr=true, imm)) + | MCollectionConcat(t, collections) => + MCollectionConcat(t, List.map(handle_imm, collections)) | MStore(binds) => MStore( List.map( diff --git a/compiler/src/codegen/mashtree.re b/compiler/src/codegen/mashtree.re index 0858974a78..30f80d111c 100644 --- a/compiler/src/codegen/mashtree.re +++ b/compiler/src/codegen/mashtree.re @@ -474,6 +474,7 @@ and instr_desc = | MAdtOp(adt_op, immediate) | MRecordOp(record_op, immediate) | MClosureOp(closure_op, immediate) + | MCollectionConcat(Typedtree.collection_concat_type, list(immediate)) | MStore(list((binding, instr))) /* Items in the same list have their backpatching delayed until the end of that list */ | MSet(binding, instr) | MDrop(instr) /* Ignore the result of an expression. Used for sequences. */ diff --git a/compiler/src/codegen/transl_anf.re b/compiler/src/codegen/transl_anf.re index e9e023733f..921bfd553f 100644 --- a/compiler/src/codegen/transl_anf.re +++ b/compiler/src/codegen/transl_anf.re @@ -545,6 +545,8 @@ let rec compile_comp = (~id=?, env, c) => { MRecordSet(idx, compile_imm(env, arg)), compile_imm(env, record), ) + | CCollectionConcat(t, lists) => + MCollectionConcat(t, List.map(compile_imm(env), lists)) | CLambda(name, args, body, closure_status) => let (body, return_type) = body; let body = (body, [return_type]); diff --git a/compiler/src/formatting/debug.re b/compiler/src/formatting/debug.re index 6c9288664b..ffd5666760 100644 --- a/compiler/src/formatting/debug.re +++ b/compiler/src/formatting/debug.re @@ -61,6 +61,8 @@ let debug_expression = (expr: Parsetree.expression) => { print_loc("PExpRecordGet", expr.pexp_loc) | PExpRecordSet(expression, {txt, _}, expression2) => print_loc("PExpRecordSet", expr.pexp_loc) + | PExpCollectionConcat(t, colls) => + print_loc("PExpCollectionConcat", expr.pexp_loc) | PExpMatch(expression, match_branches) => print_loc("PExpMatch", expr.pexp_loc) | PExpPrim0(prim0) => print_loc("PExpPrim0", expr.pexp_loc) diff --git a/compiler/src/formatting/format.re b/compiler/src/formatting/format.re index 14e9f94ce0..0c968a37b1 100644 --- a/compiler/src/formatting/format.re +++ b/compiler/src/formatting/format.re @@ -1131,6 +1131,52 @@ and resugar_pattern_list_inner = (patterns: list(Parsetree.pattern)) => { }; } +and process_list_comments = items => { + // We have to compose this list by hand because of the complexity of if a list item + // is followed by a comment, the comma must come before the comment. + // It also impacts how we force a new line for a line ending comment at the end of a list + // without introducing an extra blank line when bringing the indentation back in again + + let last_line_breaks_for_comments = ref(false); + let items_length = List.length(items); + let list_items = + List.mapi( + (i, (item, item_comments)) => { + let final_item = items_length - 1 == i; + + let comment_doc = + switch (item_comments) { + | [] => + last_line_breaks_for_comments := false; + if (final_item) { + Doc.nil; + } else { + Doc.concat([Doc.comma, Doc.line]); + }; + | _ => + let trailing_comments = + List.map( + (cmt: Parsetree.comment) => + Doc.concat([Doc.space, Comment_utils.comment_to_doc(cmt)]), + item_comments, + ); + + last_line_breaks_for_comments := true; + Doc.concat([ + Doc.comma, + Doc.concat(trailing_comments), + if (final_item) {Doc.nil} else {Doc.hardLine}, + ]); + }; + + Doc.concat([Doc.group(item), comment_doc]); + }, + items, + ); + + (last_line_breaks_for_comments^, list_items); +} + and resugar_list = ( ~original_source: array(string), @@ -1219,47 +1265,8 @@ and resugar_list = processed_list, ); - // We have to compose this list by hand because of the complexity of if a list item - // is followed by a comment, the comma must come before the comment. - // It also impacts how we force a new line for a line ending comment at the end of a list - // without introducing an extra blank line when bringing the indentation back in again - - let last_line_breaks_for_comments = ref(false); - let items_length = List.length(items); - let list_items = - List.mapi( - (i, (item, item_comments)) => { - let final_item = items_length - 1 == i; - - let comment_doc = - switch (item_comments) { - | [] => - last_line_breaks_for_comments := false; - if (final_item) { - Doc.nil; - } else { - Doc.concat([Doc.comma, Doc.line]); - }; - | _ => - let trailing_comments = - List.map( - (cmt: Parsetree.comment) => - Doc.concat([Doc.space, Comment_utils.comment_to_doc(cmt)]), - item_comments, - ); - - last_line_breaks_for_comments := true; - Doc.concat([ - Doc.comma, - Doc.concat(trailing_comments), - if (final_item) {Doc.nil} else {Doc.hardLine}, - ]); - }; - - Doc.concat([Doc.group(item), comment_doc]); - }, - items, - ); + let (last_line_breaks_for_comments, list_items) = + process_list_comments(items); Doc.group( Doc.concat([ @@ -1268,18 +1275,14 @@ and resugar_list = Doc.concat([ Doc.softLine, Doc.concat(list_items), - if (last_item_was_spread^ || last_line_breaks_for_comments^) { + if (last_item_was_spread^ || last_line_breaks_for_comments) { Doc.nil; } else { Doc.ifBreaks(Doc.comma, Doc.nil); }, ]), ), - if (last_line_breaks_for_comments^) { - Doc.hardLine; - } else { - Doc.softLine; - }, + if (last_line_breaks_for_comments) {Doc.hardLine} else {Doc.softLine}, Doc.rbracket, ]), ); @@ -2968,6 +2971,115 @@ and print_expression_inner = print_ident(txt), ]); print_assignment(~original_source, ~comments, left, expression2); + | PExpCollectionConcat(concat_t, colls) => + let list_length = List.length(colls); + + let items = + List.mapi( + (index, (t, e)) => { + // Do we have any comments on this line? + // If so, we break the whole list + + // we might have a list list [1, 2 // comment + // 3] + // so need to use the comment after the last item + // [1, + // 2, //comment + // 3] + let end_line_comments = + if (index < list_length - 2) { + let (_, next_e) = List.nth(colls, index + 1); + Comment_utils.get_comments_between_locations( + ~loc1=e.Parsetree.pexp_loc, + ~loc2=next_e.pexp_loc, + comments, + ); + } else { + let (_, item_line, item_char, _) = + Locations.get_raw_pos_info(e.pexp_loc.loc_end); + Comment_utils.get_comments_on_line_end( + ~line=item_line, + ~char=item_char, + comments, + ); + }; + + let expr = + switch (t) { + | Parsetree.PExpNonSpreadExpr => + let e = + switch (concat_t, e.pexp_desc) { + | ( + PExpListConcat, + Parsetree.PExpConstruct( + {txt: IdentName({txt: "[...]"})}, + PExpConstrTuple([expr, _]), + ), + ) + | (PExpArrayConcat, Parsetree.PExpArray([expr])) => expr + | _ => + failwith( + "Impossible: formatter: non-spread concat item containing invalid data", + ) + }; + + print_expression( + ~expression_parent=GenericExpression, + ~original_source, + ~comments= + Comment_utils.get_comments_inside_location( + ~location=e.pexp_loc, + comments, + ), + e, + ); + | Parsetree.PExpSpreadExpr => + Doc.concat([ + Doc.text("..."), + print_expression( + ~expression_parent=GenericExpression, + ~original_source, + ~comments= + Comment_utils.get_comments_inside_location( + ~location=e.pexp_loc, + comments, + ), + e, + ), + ]) + }; + (expr, end_line_comments); + }, + colls, + ); + + let (last_line_breaks_for_comments, list_items) = + process_list_comments(items); + + Doc.group( + Doc.concat([ + Doc.lbracket, + switch (concat_t) { + | Parsetree.PExpListConcat => Doc.nil + | Parsetree.PExpArrayConcat => Doc.text("> ") + }, + Doc.indent( + Doc.concat([ + Doc.softLine, + Doc.concat(list_items), + if (last_line_breaks_for_comments) { + Doc.nil; + } else { + Doc.ifBreaks(Doc.comma, Doc.nil); + }, + ]), + ), + if (last_line_breaks_for_comments) {Doc.hardLine} else { + Doc.softLine + }, + Doc.rbracket, + ]), + ); | PExpMatch(expression, match_branches) => let arg = Doc.concat([ diff --git a/compiler/src/middle_end/analyze_free_vars.re b/compiler/src/middle_end/analyze_free_vars.re index cab678aa42..4e4aed200c 100644 --- a/compiler/src/middle_end/analyze_free_vars.re +++ b/compiler/src/middle_end/analyze_free_vars.re @@ -109,6 +109,12 @@ module FreeVarsArg: Anf_iterator.IterArgument = { Ident.Set.empty, [arg1, arg2, arg3], ) + | CCollectionConcat(t, colls) => + List.fold_left( + (acc, a) => Ident.Set.union(imm_free_vars(a), acc), + Ident.Set.empty, + colls, + ) | CRecord(_, _, args) => List.fold_left( (acc, (_, a)) => Ident.Set.union(imm_free_vars(a), acc), diff --git a/compiler/src/middle_end/analyze_purity.re b/compiler/src/middle_end/analyze_purity.re index 67766329ba..9a3ad7c950 100644 --- a/compiler/src/middle_end/analyze_purity.re +++ b/compiler/src/middle_end/analyze_purity.re @@ -122,6 +122,7 @@ module PurityArg: Anf_iterator.IterArgument = { | CTuple(_) | CAdt(_) | CRecord(_) + | CCollectionConcat(_) | CGetTupleItem(_) => true | CSetTupleItem(_) => false | CGetAdtItem(_) diff --git a/compiler/src/middle_end/analyze_tail_calls.re b/compiler/src/middle_end/analyze_tail_calls.re index 8e053ee189..2d00d41b55 100644 --- a/compiler/src/middle_end/analyze_tail_calls.re +++ b/compiler/src/middle_end/analyze_tail_calls.re @@ -84,6 +84,7 @@ let rec analyze_comp_expression = | CPrim1(_) | CPrim2(_) | CPrimN(_) + | CCollectionConcat(_) | CImmExpr(_) => false } diff --git a/compiler/src/middle_end/anf_helper.re b/compiler/src/middle_end/anf_helper.re index b0ac79ba72..18812251d8 100644 --- a/compiler/src/middle_end/anf_helper.re +++ b/compiler/src/middle_end/anf_helper.re @@ -146,6 +146,14 @@ module Comp = { ~env?, CSetRecordItem(idx, record, arg), ); + let collection_concat = (~loc=?, ~attributes=?, ~env=?, t, colls) => + mk( + ~loc?, + ~attributes?, + ~allocation_type=Managed, + ~env?, + CCollectionConcat(t, colls), + ); let if_ = (~loc=?, ~attributes=?, ~allocation_type, ~env=?, cond, tru, fals) => mk(~loc?, ~attributes?, ~allocation_type, ~env?, CIf(cond, tru, fals)); let for_ = (~loc=?, ~attributes=?, ~env=?, cond, inc, body) => diff --git a/compiler/src/middle_end/anf_helper.rei b/compiler/src/middle_end/anf_helper.rei index 30e5bfff4d..ba04165aca 100644 --- a/compiler/src/middle_end/anf_helper.rei +++ b/compiler/src/middle_end/anf_helper.rei @@ -168,6 +168,15 @@ module Comp: { imm_expression ) => comp_expression; + let collection_concat: + ( + ~loc: loc=?, + ~attributes: attributes=?, + ~env: env=?, + Typedtree.collection_concat_type, + list(imm_expression) + ) => + comp_expression; let record: ( ~loc: loc=?, diff --git a/compiler/src/middle_end/anf_iterator.re b/compiler/src/middle_end/anf_iterator.re index 32b72e6f43..2876f803d7 100644 --- a/compiler/src/middle_end/anf_iterator.re +++ b/compiler/src/middle_end/anf_iterator.re @@ -83,6 +83,7 @@ module MakeIter = (Iter: IterArgument) => { | CSetRecordItem(_, record, arg) => iter_imm_expression(record); iter_imm_expression(arg); + | CCollectionConcat(_, colls) => List.iter(iter_imm_expression, colls) | CIf(c, t, f) => iter_imm_expression(c); iter_anf_expression(t); diff --git a/compiler/src/middle_end/anf_mapper.re b/compiler/src/middle_end/anf_mapper.re index 469d4c1852..35b741f28a 100644 --- a/compiler/src/middle_end/anf_mapper.re +++ b/compiler/src/middle_end/anf_mapper.re @@ -196,6 +196,10 @@ module MakeMap = (Iter: MapArgument) => { process_imm_expression(arg), ), ) + | CCollectionConcat(t, colls) => + leave_with( + CCollectionConcat(t, List.map(process_imm_expression, colls)), + ) | CIf(cond, t, f) => let cond = process_imm_expression(cond); push_input( diff --git a/compiler/src/middle_end/anftree.re b/compiler/src/middle_end/anftree.re index 3134363366..361c168646 100644 --- a/compiler/src/middle_end/anftree.re +++ b/compiler/src/middle_end/anftree.re @@ -367,6 +367,7 @@ and comp_expression_desc = | CGetAdtTag(imm_expression) | CGetRecordItem(int32, imm_expression) | CSetRecordItem(int32, imm_expression, imm_expression) + | CCollectionConcat(Typedtree.collection_concat_type, list(imm_expression)) | CIf(imm_expression, anf_expression, anf_expression) | CFor(option(anf_expression), option(anf_expression), anf_expression) | CContinue diff --git a/compiler/src/middle_end/anftree.rei b/compiler/src/middle_end/anftree.rei index 007bf2baee..fa36025632 100644 --- a/compiler/src/middle_end/anftree.rei +++ b/compiler/src/middle_end/anftree.rei @@ -347,6 +347,7 @@ and comp_expression_desc = | CGetAdtTag(imm_expression) | CGetRecordItem(int32, imm_expression) | CSetRecordItem(int32, imm_expression, imm_expression) + | CCollectionConcat(Typedtree.collection_concat_type, list(imm_expression)) | CIf(imm_expression, anf_expression, anf_expression) | CFor(option(anf_expression), option(anf_expression), anf_expression) | CContinue diff --git a/compiler/src/middle_end/linearize.re b/compiler/src/middle_end/linearize.re index 1ebceb3f3c..be1acd60c7 100644 --- a/compiler/src/middle_end/linearize.re +++ b/compiler/src/middle_end/linearize.re @@ -929,6 +929,20 @@ let rec transl_imm = ), ], ); + | TExpCollectionConcat(t, colls) => + let tmp = gensym("catcollection"); + let (new_args, new_setup) = List.split(List.map(transl_imm, colls)); + ( + Imm.id(~loc, ~env, tmp), + List.concat(new_setup) + @ [ + BLet( + tmp, + Comp.collection_concat(~loc, ~env, t, new_args), + Nonglobal, + ), + ], + ); | TExpMatch(exp, branches, partial) => let tmp = gensym("match"); let (exp_ans, exp_setup) = transl_imm(exp); diff --git a/compiler/src/parsing/ast_helper.re b/compiler/src/parsing/ast_helper.re index a286b868c9..a1fb050402 100644 --- a/compiler/src/parsing/ast_helper.re +++ b/compiler/src/parsing/ast_helper.re @@ -24,6 +24,10 @@ type listitem('a) = | ListItem('a) | ListSpread('a, Location.t); +type arrayitem = + | ArrayItem(expression) + | ArraySpread(expression, Location.t); + type recorditem = | RecordItem(loc(Identifier.t), expression) | RecordSpread(expression, Location.t); @@ -415,6 +419,8 @@ module Expression = { }; let block = (~loc=?, ~attributes=?, a) => mk(~loc?, ~attributes?, PExpBlock(a)); + let collection_concat = (~loc, ~attributes=?, a, b) => + mk(~loc, ~attributes?, PExpCollectionConcat(a, b)); let list = (~loc, ~attributes=?, a) => { let empty = tuple_construct(~loc, ident_empty, []); let list = @@ -427,25 +433,111 @@ module Expression = { tuple_construct(~loc, ~attributes?, ident_cons, [expr, empty]) | ListSpread(expr, _) => expr }; - List.fold_left( - (acc, expr) => { + if (List.exists( + expr => + switch (expr) { + | ListSpread(_) => true + | ListItem(_) => false + }, + rest, + )) { + collection_concat( + ~loc, + ~attributes?, + PExpListConcat, + List.map( + expr => + switch (expr) { + | ListSpread(expr, loc) => ( + PExpSpreadExpr, + {...expr, pexp_loc: loc}, + ) + | ListItem(expr) => ( + PExpNonSpreadExpr, + // Still convert to a single-element list to make later compilation steps easier + tuple_construct( + ~loc=expr.pexp_loc, + ~attributes?, + ident_cons, + [ + expr, + tuple_construct(~loc=expr.pexp_loc, ident_empty, []), + ], + ), + ) + }, + a, + ), + ); + } else { + List.fold_left( + (acc, expr) => { + switch (expr) { + | ListItem(expr) => + tuple_construct(~loc, ~attributes?, ident_cons, [expr, acc]) + | ListSpread(_, loc) => + failwith( + "Impossible: non-final list spread when existence has been disproven", + ) + } + }, + base, + rest, + ); + }; + }; + {...list, pexp_loc: loc}; + }; + let array_items = (~loc, ~attributes=?, a) => { + let no_spreads = + List.for_all( + x => { + switch (x) { + | ArrayItem(_) => true + | ArraySpread(_) => false + } + }, + a, + ); + if (no_spreads) { + array( + ~loc, + ~attributes?, + List.map( + expr => switch (expr) { - | ListItem(expr) => - tuple_construct(~loc, ~attributes?, ident_cons, [expr, acc]) - | ListSpread(_, loc) => - raise( - SyntaxError( - loc, - "A list spread can only appear at the end of a list.", - ), + | ArraySpread(_) => + failwith( + "Impossible: spread in array when existence has been disproven", + ) + | ArrayItem(expr) => expr + }, + a, + ), + ); + } else { + collection_concat( + ~loc, + ~attributes?, + PExpArrayConcat, + List.map( + x => { + switch (x) { + | ArrayItem(expr) => ( + PExpNonSpreadExpr, + // Still convert to a single-element array to make later compilation steps easier + array(~loc=expr.pexp_loc, ~attributes?, [expr]), + ) + | ArraySpread(expr, loc) => ( + PExpSpreadExpr, + {...expr, pexp_loc: loc}, ) } }, - base, - rest, - ); - }; - {...list, pexp_loc: loc}; + a, + ), + ); + }; }; let ignore = e => diff --git a/compiler/src/parsing/ast_helper.rei b/compiler/src/parsing/ast_helper.rei index fad12f60c7..7f0b271317 100644 --- a/compiler/src/parsing/ast_helper.rei +++ b/compiler/src/parsing/ast_helper.rei @@ -24,6 +24,10 @@ type listitem('a) = | ListItem('a) | ListSpread('a, Location.t); +type arrayitem = + | ArrayItem(expression) + | ArraySpread(expression, Location.t); + type recorditem = | RecordItem(loc(Identifier.t), expression) | RecordSpread(expression, Location.t); @@ -158,6 +162,16 @@ module Expression: { let list: (~loc: loc, ~attributes: attributes=?, list(listitem(expression))) => expression; + let array_items: + (~loc: loc, ~attributes: attributes=?, list(arrayitem)) => expression; + let collection_concat: + ( + ~loc: loc, + ~attributes: attributes=?, + collection_concat_type, + list((collection_concat_expression_type, expression)) + ) => + expression; let array: (~loc: loc=?, ~attributes: attributes=?, list(expression)) => expression; let array_get: diff --git a/compiler/src/parsing/ast_mapper.re b/compiler/src/parsing/ast_mapper.re index 6173547277..dafa69d8ce 100644 --- a/compiler/src/parsing/ast_mapper.re +++ b/compiler/src/parsing/ast_mapper.re @@ -87,6 +87,13 @@ module E = { map_loc(sub, f), sub.expr(sub, v), ) + | PExpCollectionConcat(t, colls) => + collection_concat( + ~loc, + ~attributes, + t, + List.map(((t, expr)) => (t, sub.expr(sub, expr)), colls), + ) | PExpLet(r, m, vbs) => let_(~loc, ~attributes, r, m, List.map(sub.value_binding(sub), vbs)) | PExpMatch(e, mbs) => diff --git a/compiler/src/parsing/parser.messages b/compiler/src/parsing/parser.messages index 5691fcab4f..abbbec4eac 100644 --- a/compiler/src/parsing/parser.messages +++ b/compiler/src/parsing/parser.messages @@ -1035,6 +1035,15 @@ program: MODULE UIDENT EOL LBRACE ELLIPSIS WHEN ## The known suffix of the stack is as follows: ## ELLIPSIS ## +program: MODULE UIDENT EOL LBRACKRCARET ELLIPSIS YIELD +## +## Ends in an error in state: 225. +## +## array_item -> ELLIPSIS . expr [ RBRACK EOL COMMA ] +## +## The known suffix of the stack is as follows: +## ELLIPSIS +## program: MODULE UIDENT EOL LET WASMI64 EQUAL EOL UNDERSCORE ## ## Ends in an error in state: 430. @@ -2884,22 +2893,6 @@ program: MODULE UIDENT EOL UIDENT LPAREN UIDENT ARROW ## In state 68, spurious reduction of production expr -> non_stmt_expr ## In state 366, spurious reduction of production lseparated_nonempty_list_inner(comma,expr) -> expr ## -program: MODULE UIDENT EOL UIDENT LPAREN UIDENT COMMA RBRACK -## -## Ends in an error in state: 707. -## -## construct_expr -> type_id lparen lseparated_nonempty_list_inner(comma,expr) option(comma) . rparen [ THICKARROW STAR SLASH SEMI RPAREN RCARET RBRACK RBRACE PIPE LPAREN LCARET LBRACK INFIX_90 INFIX_80 INFIX_70 INFIX_60 INFIX_50 INFIX_40 INFIX_30 INFIX_120 INFIX_110 INFIX_100 GETS EOL EOF ELSE DOT DASH COMMA COLON ] -## -## The known suffix of the stack is as follows: -## type_id lparen lseparated_nonempty_list_inner(comma,expr) option(comma) -## -## WARNING: This example involves spurious reductions. -## This implies that, although the LR(1) items shown above provide an -## accurate view of the past (what has been recognized so far), they -## may provide an INCOMPLETE view of the future (what was expected next). -## In state 43, spurious reduction of production comma -> COMMA -## In state 173, spurious reduction of production option(comma) -> comma -## Expected `)` to complete the constructor arguments. @@ -3322,25 +3315,6 @@ program: MODULE UIDENT EOL PREFIX_150 LBRACK RBRACK LBRACK BIGINT ARROW Expected `]` to complete the array index expression. -program: MODULE UIDENT EOL LBRACE BREAK RBRACE LPAREN WASMI64 COMMA RBRACK -## -## Ends in an error in state: 320. -## -## app_expr -> braced_expr lparen lseparated_nonempty_list_inner(comma,expr) option(comma) . rparen [ THICKARROW STAR SLASH SEMI RPAREN RCARET RBRACK RBRACE PIPE LPAREN LCARET LBRACK INFIX_90 INFIX_80 INFIX_70 INFIX_60 INFIX_50 INFIX_40 INFIX_30 INFIX_120 INFIX_110 INFIX_100 GETS EOL EOF ELSE DOT DASH COMMA COLON ] -## -## The known suffix of the stack is as follows: -## braced_expr lparen lseparated_nonempty_list_inner(comma,expr) option(comma) -## -## WARNING: This example involves spurious reductions. -## This implies that, although the LR(1) items shown above provide an -## accurate view of the past (what has been recognized so far), they -## may provide an INCOMPLETE view of the future (what was expected next). -## In state 95, spurious reduction of production comma -> COMMA -## In state 171, spurious reduction of production option(comma) -> comma -## - -Expected `)` to complete the function call. - program: MODULE UIDENT EOL LBRACE LET REC WHILE ## ## Ends in an error in state: 351. @@ -3772,25 +3746,6 @@ program: MODULE UIDENT EOL LBRACKRCARET WASMI64 COMMA EOL UNDERSCORE Expected an expression or `]` to complete the array expression. -program: MODULE UIDENT EOL LBRACKRCARET WASMI64 COMMA RPAREN -## -## Ends in an error in state: 177. -## -## array_expr -> lbrackrcaret lseparated_nonempty_list_inner(comma,expr) option(comma) . rbrack [ THICKARROW STAR SLASH SEMI RPAREN RCARET RBRACK RBRACE PIPE LCARET INFIX_90 INFIX_80 INFIX_70 INFIX_60 INFIX_50 INFIX_40 INFIX_30 INFIX_120 INFIX_110 INFIX_100 EOL EOF ELSE DASH COMMA COLON ] -## -## The known suffix of the stack is as follows: -## lbrackrcaret lseparated_nonempty_list_inner(comma,expr) option(comma) -## -## WARNING: This example involves spurious reductions. -## This implies that, although the LR(1) items shown above provide an -## accurate view of the past (what has been recognized so far), they -## may provide an INCOMPLETE view of the future (what was expected next). -## In state 95, spurious reduction of production comma -> COMMA -## In state 171, spurious reduction of production option(comma) -> comma -## - -Expected `]` to complete the array expression. - program: MODULE UIDENT EOL LBRACKRCARET WASMI64 EOL WHILE ## ## Ends in an error in state: 179. @@ -3810,26 +3765,30 @@ program: MODULE UIDENT EOL LBRACKRCARET WASMI64 EOL WHILE Expected `]` to complete the array expression. -program: MODULE UIDENT EOL LBRACKRCARET WASMI64 THICKARROW +program: MODULE UIDENT EOL LBRACKRCARET UIDENT ARROW ## -## Ends in an error in state: 176. +## Ends in an error in state: 417. ## -## array_expr -> lbrackrcaret lseparated_nonempty_list_inner(comma,expr) . option(comma) rbrack [ THICKARROW STAR SLASH SEMI RPAREN RCARET RBRACK RBRACE PIPE LCARET INFIX_90 INFIX_80 INFIX_70 INFIX_60 INFIX_50 INFIX_40 INFIX_30 INFIX_120 INFIX_110 INFIX_100 EOL EOF ELSE DASH COMMA COLON ] -## lseparated_nonempty_list_inner(comma,expr) -> lseparated_nonempty_list_inner(comma,expr) . comma expr [ RBRACK EOL COMMA ] +## array_expr -> lbrackrcaret lseparated_nonempty_list_inner(comma,array_item) . option(comma) rbrack [ THICKARROW STAR SLASH SEMI RPAREN RCARET RBRACK RBRACE PIPE LPAREN LCARET LBRACK INFIX_90 INFIX_80 INFIX_70 INFIX_60 INFIX_50 INFIX_40 INFIX_30 INFIX_120 INFIX_110 INFIX_100 GETS EOL EOF ELSE DOT DASH COMMA COLON ] +## lseparated_nonempty_list_inner(comma,array_item) -> lseparated_nonempty_list_inner(comma,array_item) . comma array_item [ RBRACK EOL COMMA ] ## ## The known suffix of the stack is as follows: -## lbrackrcaret lseparated_nonempty_list_inner(comma,expr) +## lbrackrcaret lseparated_nonempty_list_inner(comma,array_item) ## ## WARNING: This example involves spurious reductions. ## This implies that, although the LR(1) items shown above provide an ## accurate view of the past (what has been recognized so far), they ## may provide an INCOMPLETE view of the future (what was expected next). -## In state 42, spurious reduction of production non_assign_expr -> simple_expr -## In state 127, spurious reduction of production non_binop_expr -> non_assign_expr -## In state 71, spurious reduction of production annotated_expr -> non_binop_expr -## In state 243, spurious reduction of production non_stmt_expr -> annotated_expr -## In state 62, spurious reduction of production expr -> non_stmt_expr -## In state 266, spurious reduction of production lseparated_nonempty_list_inner(comma,expr) -> expr +## In state 200, spurious reduction of production qualified_uid -> lseparated_nonempty_list_inner(dot,type_id_str) +## In state 127, spurious reduction of production construct_expr -> qualified_uid +## In state 240, spurious reduction of production left_accessor_expr -> construct_expr +## In state 221, spurious reduction of production non_assign_expr -> left_accessor_expr +## In state 198, spurious reduction of production non_binop_expr -> non_assign_expr +## In state 152, spurious reduction of production annotated_expr -> non_binop_expr +## In state 249, spurious reduction of production non_stmt_expr -> annotated_expr +## In state 145, spurious reduction of production expr -> non_stmt_expr +## In state 421, spurious reduction of production array_item -> expr +## In state 427, spurious reduction of production lseparated_nonempty_list_inner(comma,array_item) -> array_item ## Expected a comma followed by an expression or `]` to complete the array expression. diff --git a/compiler/src/parsing/parser.mly b/compiler/src/parsing/parser.mly index fe53f4224b..8dcab375f5 100644 --- a/compiler/src/parsing/parser.mly +++ b/compiler/src/parsing/parser.mly @@ -569,9 +569,13 @@ list_expr: | lbrack rbrack { Expression.list ~loc:(to_loc $loc) [] } | lbrack lseparated_nonempty_list(comma, list_item) comma? rbrack { Expression.list ~loc:(to_loc $loc) $2 } +array_item: + | ELLIPSIS expr { ArraySpread ($2, to_loc $loc) } + | expr { ArrayItem $1 } + array_expr: | lbrackrcaret rbrack { Expression.array ~loc:(to_loc $loc) [] } - | lbrackrcaret opt_eols lseparated_nonempty_list(comma, expr) comma? rbrack { Expression.array ~loc:(to_loc $loc) $3 } + | lbrackrcaret opt_eols lseparated_nonempty_list(comma, array_item) comma? rbrack { Expression.array_items ~loc:(to_loc $loc) $3 } stmt_expr: | THROW expr { Expression.apply ~loc:(to_loc $loc) (mkid_expr $loc($1) [mkstr $loc($1) "throw"]) [{paa_label=Unlabeled; paa_expr=$2; paa_loc=(to_loc $loc($2))}] } diff --git a/compiler/src/parsing/parsetree.re b/compiler/src/parsing/parsetree.re index 98870653db..e8192624b7 100644 --- a/compiler/src/parsing/parsetree.re +++ b/compiler/src/parsing/parsetree.re @@ -490,6 +490,16 @@ type expression = { pexp_loc: Location.t, } +[@deriving (sexp, yojson)] +and collection_concat_type = + | PExpListConcat + | PExpArrayConcat + +[@deriving (sexp, yojson)] +and collection_concat_expression_type = + | PExpSpreadExpr + | PExpNonSpreadExpr + [@deriving (sexp, yojson)] and expression_desc = | PExpId(loc(Identifier.t)) @@ -501,6 +511,10 @@ and expression_desc = | PExpRecord(option(expression), list((loc(Identifier.t), expression))) | PExpRecordGet(expression, loc(Identifier.t)) | PExpRecordSet(expression, loc(Identifier.t), expression) + | PExpCollectionConcat( + collection_concat_type, + list((collection_concat_expression_type, expression)), + ) | PExpLet(rec_flag, mut_flag, list(value_binding)) | PExpMatch(expression, list(match_branch)) | PExpPrim0(prim0) diff --git a/compiler/src/parsing/parsetree_iter.re b/compiler/src/parsing/parsetree_iter.re index c627b799d2..6bc7adc179 100644 --- a/compiler/src/parsing/parsetree_iter.re +++ b/compiler/src/parsing/parsetree_iter.re @@ -248,6 +248,8 @@ and iter_expression = iter_expression(hooks, e); iter_loc(hooks, f); iter_expression(hooks, v); + | PExpCollectionConcat(_, collections) => + iter_expressions(hooks, List.map(snd, collections)) | PExpLet(r, m, vbs) => iter_let(hooks, r, m, vbs) | PExpMatch(e, mbs) => iter_expression(hooks, e); diff --git a/compiler/src/typed/typecore.re b/compiler/src/typed/typecore.re index 18bbe70fbe..46860d3ded 100644 --- a/compiler/src/typed/typecore.re +++ b/compiler/src/typed/typecore.re @@ -979,6 +979,27 @@ and type_expect_ = exp_type: Builtin_types.type_void, exp_env: env, }); + | PExpCollectionConcat(t, colls) => + let (mk_builtin_type, texp_type) = + switch (t) { + | PExpListConcat => (Builtin_types.type_list, TExpListConcat) + | PExpArrayConcat => (Builtin_types.type_array, TExpArrayConcat) + }; + let coll_type = mk_builtin_type(newvar()); + with_explanation(() => unify_exp_types(loc, env, coll_type, ty_expected)); + let typed_colls = + List.map( + ((_, expr)) => type_expect(env, expr, mk_expected(coll_type)), + colls, + ); + rue({ + exp_desc: TExpCollectionConcat(texp_type, typed_colls), + exp_loc: loc, + exp_extra: [], + exp_attributes: attributes, + exp_type: coll_type, + exp_env: env, + }); | PExpLet(rec_flag, mut_flag, pats) => let scp = None; let (pat_exp_list, new_env, unpacks) = diff --git a/compiler/src/typed/typedtree.re b/compiler/src/typed/typedtree.re index 89bb3139ff..b0bc8df11c 100644 --- a/compiler/src/typed/typedtree.re +++ b/compiler/src/typed/typedtree.re @@ -459,6 +459,11 @@ type expression = { and exp_extra = | TExpConstraint(core_type) +[@deriving sexp] +and collection_concat_type = + | TExpListConcat + | TExpArrayConcat + [@deriving sexp] and expression_desc = | TExpIdent(Path.t, loc(Identifier.t), Types.value_description) @@ -478,6 +483,7 @@ and expression_desc = Types.label_description, expression, ) + | TExpCollectionConcat(collection_concat_type, list(expression)) | TExpLet(rec_flag, mut_flag, list(value_binding)) | TExpMatch(expression, list(match_branch), partial) | TExpUse(loc(Path.t), use_items) diff --git a/compiler/src/typed/typedtree.rei b/compiler/src/typed/typedtree.rei index 0bc164457b..0b971678b4 100644 --- a/compiler/src/typed/typedtree.rei +++ b/compiler/src/typed/typedtree.rei @@ -428,6 +428,10 @@ type expression = { and exp_extra = | TExpConstraint(core_type) +and collection_concat_type = + | TExpListConcat + | TExpArrayConcat + and expression_desc = | TExpIdent(Path.t, loc(Identifier.t), Types.value_description) | TExpConstant(constant) @@ -446,6 +450,7 @@ and expression_desc = Types.label_description, expression, ) + | TExpCollectionConcat(collection_concat_type, list(expression)) | TExpLet(rec_flag, mut_flag, list(value_binding)) | TExpMatch(expression, list(match_branch), partial) | TExpUse(loc(Path.t), use_items) diff --git a/compiler/src/typed/typedtreeIter.re b/compiler/src/typed/typedtreeIter.re index 2e1a2d6b1d..1e04205444 100644 --- a/compiler/src/typed/typedtreeIter.re +++ b/compiler/src/typed/typedtreeIter.re @@ -247,6 +247,7 @@ module MakeIterator = iter_expression(a1); iter_expression(a2); iter_expression(a3); + | TExpCollectionConcat(_, colls) => List.iter(iter_expression, colls) | TExpIf(c, t, f) => iter_expression(c); iter_expression(t); diff --git a/compiler/src/typed/typedtreeMap.re b/compiler/src/typed/typedtreeMap.re index d2dad743a4..b402079065 100644 --- a/compiler/src/typed/typedtreeMap.re +++ b/compiler/src/typed/typedtreeMap.re @@ -249,6 +249,8 @@ module MakeMap = ld, map_expression(arg), ) + | TExpCollectionConcat(t, colls) => + TExpCollectionConcat(t, List.map(map_expression, colls)) | TExpBlock(args) => TExpBlock(List.map(map_expression, args)) | TExpConstruct(a, b, TExpConstrTuple(args)) => TExpConstruct(a, b, TExpConstrTuple(List.map(map_expression, args))) diff --git a/compiler/test/grainfmt/spreads.expected.gr b/compiler/test/grainfmt/spreads.expected.gr index ee4112842f..de555771ab 100644 --- a/compiler/test/grainfmt/spreads.expected.gr +++ b/compiler/test/grainfmt/spreads.expected.gr @@ -37,3 +37,27 @@ let rec reject2 = (fn, list) => { }, } } + +let l = ["a", "b", "c"] +let a = [ + ...l, + "secondInAVeryLongString", + "thirdInAStringThatWillBreakLine", + ...l, +] + +let a = [ + ...l, /* before first */ // first comment + "second", // second comment + "third", /* before third */ // third comment + ...l, // fourth comment +] // after comment + +let a = [> "a", "b", "c"] +let arr = [> ...a, "second", "third", ...a] +let arr = [> + ...a, /* before comment 1 */ // comment 1 + "second", // comment 2 + "third", /* before comment 3 */ // comment 3 + ...a, /* comment 4 */ +] // after comment diff --git a/compiler/test/grainfmt/spreads.input.gr b/compiler/test/grainfmt/spreads.input.gr index 68d15410bc..526c876b0a 100644 --- a/compiler/test/grainfmt/spreads.input.gr +++ b/compiler/test/grainfmt/spreads.input.gr @@ -31,3 +31,21 @@ let rec reject2 = (fn, list) => { } } } + +let l = ["a", "b", "c"] +let a = [...l, "secondInAVeryLongString", "thirdInAStringThatWillBreakLine", ...l] + +let a = [ +...l /* before first */, // first comment +"second", // second comment +"third" /* before third */, // third comment +...l // fourth comment +] // after comment + +let a = [> "a", "b", "c"] +let arr = [>...a,"second","third",...a] +let arr = [> +...a /* before comment 1 */, // comment 1 +"second", // comment 2 +"third" /* before comment 3 */, // comment 3 +...a /* comment 4 */] // after comment diff --git a/compiler/test/suites/arrays.re b/compiler/test/suites/arrays.re index 955f1575a7..17865152b0 100644 --- a/compiler/test/suites/arrays.re +++ b/compiler/test/suites/arrays.re @@ -114,6 +114,27 @@ describe("arrays", ({test, testSkip}) => { "[> ,]", "Error: Syntax error", ); + // spread syntax + assertRun( + "array_spread1", + "let a = [> 1, 2, 3]; let b = [> 4, 5, 6]; print([> ...a, ...b])", + "[> 1, 2, 3, 4, 5, 6]\n", + ); + assertRun( + "array_spread2", + "let a = [> 1, 2, 3]; print([> 0, ...a])", + "[> 0, 1, 2, 3]\n", + ); + assertRun( + "array_spread3", + "let a = [> 1, 2, 3]; print([> ...a])", + "[> 1, 2, 3]\n", + ); + assertCompileError( + "array_spread4", + "let a = [> 1, 2, 3]; [> ...a, \"a\"]", + "has type String but", + ); // parsing Grain_parsing.( Ast_helper.( diff --git a/compiler/test/suites/lists.re b/compiler/test/suites/lists.re index 8a63a42b98..0990bfe8c5 100644 --- a/compiler/test/suites/lists.re +++ b/compiler/test/suites/lists.re @@ -22,4 +22,25 @@ describe("lists", ({test, testSkip}) => { assertSnapshot("list1_trailing", "[1, 2, 3,]"); assertSnapshot("list1_trailing_space", "[1, 2, 3, ]"); assertCompileError("invalid_empty_trailing", "[,]", "Error: Syntax error"); + // arbitrary-position spread + assertRun( + "list_spread_anywhere1", + "let a = [1, 2]; print([...a, 3, 4])", + "[1, 2, 3, 4]\n", + ); + assertRun( + "list_spread_anywhere2", + "let a = [1, 2]; let b = [4, 5]; print([...a, 3, ...b])", + "[1, 2, 3, 4, 5]\n", + ); + assertRun( + "list_spread_anywhere3", + "let a = [1, 2, 3]; print([...a])", + "[1, 2, 3]\n", + ); + assertCompileError( + "list_spread_anywhere4", + "let a = [1, 2]; [...a, \"a\"]", + "has type String but", + ); }); diff --git a/stdlib/pervasives.gr b/stdlib/pervasives.gr index 0681c4a94e..de2682698a 100644 --- a/stdlib/pervasives.gr +++ b/stdlib/pervasives.gr @@ -13,6 +13,7 @@ module Pervasives include "runtime/exception" include "runtime/unsafe/memory" include "runtime/unsafe/wasmi32" +include "runtime/concat" include "runtime/equal" from Equal use { equal as (==) } diff --git a/stdlib/runtime/concat.gr b/stdlib/runtime/concat.gr new file mode 100644 index 0000000000..5bfd3988bd --- /dev/null +++ b/stdlib/runtime/concat.gr @@ -0,0 +1,65 @@ +/* grainc-flags --no-pervasives */ +module Concat + +include "runtime/unsafe/wasmi32" +from WasmI32 use { (+) } +include "runtime/unsafe/memory" +include "runtime/dataStructures" +from DataStructures use { tagSimpleNumber, allocateArray } + +let rec append = (list1, list2) => { + match (list1) { + [] => list2, + [first, ...rest] => [first, ...append(rest, list2)], + } +} + +@unsafe +provide let listConcat = listsArray => { + let ptr = WasmI32.fromGrain(listsArray) + let length = WasmI32.load(ptr, 4n) + let mut result = [] + + for (let mut i = 0n; WasmI32.ltU(i, length); i += 1n) { + let list = listsArray[tagSimpleNumber(i)] + result = append(result, list) + } + result +} + +@unsafe +let findLength = arr => { + let ptr = WasmI32.fromGrain(arr) + WasmI32.load(ptr, 4n) +} + +@unsafe +provide let arrayConcat = (arraysArray: Array>) => { + let numArrs = findLength(arraysArray) + + let mut totLength = 0n + for (let mut i = 0n; WasmI32.ltU(i, numArrs); i += 1n) { + let array = arraysArray[tagSimpleNumber(i)] + totLength += findLength(array) + } + + let result = allocateArray(totLength) + let mut byteOffset = result + for (let mut i = 0n; WasmI32.ltU(i, numArrs); i += 1n) { + let array = arraysArray[tagSimpleNumber(i)] + let length = findLength(array) + for ( + let mut arrI = 0n; + WasmI32.ltU(arrI, length); + arrI += 1n + ) { + WasmI32.store( + byteOffset, + Memory.incRef(WasmI32.fromGrain(array[tagSimpleNumber(arrI)])), + 8n + ) + byteOffset += 4n + } + } + WasmI32.toGrain(result): Array +} From 7af1d6a7994d1736aa081881335135c8643d4899 Mon Sep 17 00:00:00 2001 From: Alex Snezhko Date: Sat, 11 Feb 2023 16:56:44 -0500 Subject: [PATCH 2/3] changed concat functionality, other minor changes --- compiler/src/codegen/compcore.re | 6 +- compiler/src/formatting/debug.re | 2 +- compiler/src/formatting/format.re | 8 +- compiler/src/middle_end/analyze_free_vars.re | 4 +- compiler/src/middle_end/anf_helper.re | 4 +- compiler/src/middle_end/anf_iterator.re | 3 +- compiler/src/middle_end/anf_mapper.re | 4 +- compiler/src/middle_end/linearize.re | 5 +- compiler/src/parsing/ast_helper.re | 141 +++++++++++-------- compiler/src/parsing/ast_mapper.re | 4 +- compiler/src/typed/typecore.re | 19 +-- compiler/src/typed/typedtreeIter.re | 3 +- compiler/src/typed/typedtreeMap.re | 4 +- compiler/test/suites/arrays.re | 4 +- compiler/test/suites/lists.re | 4 +- stdlib/runtime/concat.gr | 58 ++++---- stdlib/runtime/concat.md | 16 +++ 17 files changed, 170 insertions(+), 119 deletions(-) create mode 100644 stdlib/runtime/concat.md diff --git a/compiler/src/codegen/compcore.re b/compiler/src/codegen/compcore.re index 0dccac74eb..fa730204ab 100644 --- a/compiler/src/codegen/compcore.re +++ b/compiler/src/codegen/compcore.re @@ -3016,14 +3016,14 @@ and compile_instr = (wasm_mod, env, instr) => compile_record_op(wasm_mod, env, record, record_op) | MClosureOp(closure_op, closure) => compile_closure_op(wasm_mod, env, closure, closure_op) - | MCollectionConcat(t, colls) => - let colls_arr = allocate_array(wasm_mod, env, colls); + | MCollectionConcat(t, collections) => + let collections_arr = allocate_array(wasm_mod, env, collections); let concat = switch (t) { | TExpListConcat => call_list_concat | TExpArrayConcat => call_array_concat }; - concat(wasm_mod, env, [colls_arr]); + concat(wasm_mod, env, [collections_arr]); | MPrim0(p0) => compile_prim0(wasm_mod, env, p0) | MPrim1(p1, arg) => compile_prim1(wasm_mod, env, p1, arg, instr.instr_loc) | MPrim2(p2, arg1, arg2) => compile_prim2(wasm_mod, env, p2, arg1, arg2) diff --git a/compiler/src/formatting/debug.re b/compiler/src/formatting/debug.re index ffd5666760..4ca4f12fae 100644 --- a/compiler/src/formatting/debug.re +++ b/compiler/src/formatting/debug.re @@ -61,7 +61,7 @@ let debug_expression = (expr: Parsetree.expression) => { print_loc("PExpRecordGet", expr.pexp_loc) | PExpRecordSet(expression, {txt, _}, expression2) => print_loc("PExpRecordSet", expr.pexp_loc) - | PExpCollectionConcat(t, colls) => + | PExpCollectionConcat(t, collections) => print_loc("PExpCollectionConcat", expr.pexp_loc) | PExpMatch(expression, match_branches) => print_loc("PExpMatch", expr.pexp_loc) diff --git a/compiler/src/formatting/format.re b/compiler/src/formatting/format.re index 0c968a37b1..89021a204f 100644 --- a/compiler/src/formatting/format.re +++ b/compiler/src/formatting/format.re @@ -2971,8 +2971,8 @@ and print_expression_inner = print_ident(txt), ]); print_assignment(~original_source, ~comments, left, expression2); - | PExpCollectionConcat(concat_t, colls) => - let list_length = List.length(colls); + | PExpCollectionConcat(concat_t, collections) => + let list_length = List.length(collections); let items = List.mapi( @@ -2988,7 +2988,7 @@ and print_expression_inner = // 3] let end_line_comments = if (index < list_length - 2) { - let (_, next_e) = List.nth(colls, index + 1); + let (_, next_e) = List.nth(collections, index + 1); Comment_utils.get_comments_between_locations( ~loc1=e.Parsetree.pexp_loc, ~loc2=next_e.pexp_loc, @@ -3050,7 +3050,7 @@ and print_expression_inner = }; (expr, end_line_comments); }, - colls, + collections, ); let (last_line_breaks_for_comments, list_items) = diff --git a/compiler/src/middle_end/analyze_free_vars.re b/compiler/src/middle_end/analyze_free_vars.re index 4e4aed200c..f4fdc11049 100644 --- a/compiler/src/middle_end/analyze_free_vars.re +++ b/compiler/src/middle_end/analyze_free_vars.re @@ -109,11 +109,11 @@ module FreeVarsArg: Anf_iterator.IterArgument = { Ident.Set.empty, [arg1, arg2, arg3], ) - | CCollectionConcat(t, colls) => + | CCollectionConcat(t, collections) => List.fold_left( (acc, a) => Ident.Set.union(imm_free_vars(a), acc), Ident.Set.empty, - colls, + collections, ) | CRecord(_, _, args) => List.fold_left( diff --git a/compiler/src/middle_end/anf_helper.re b/compiler/src/middle_end/anf_helper.re index 18812251d8..ec9431ecf6 100644 --- a/compiler/src/middle_end/anf_helper.re +++ b/compiler/src/middle_end/anf_helper.re @@ -146,13 +146,13 @@ module Comp = { ~env?, CSetRecordItem(idx, record, arg), ); - let collection_concat = (~loc=?, ~attributes=?, ~env=?, t, colls) => + let collection_concat = (~loc=?, ~attributes=?, ~env=?, t, collections) => mk( ~loc?, ~attributes?, ~allocation_type=Managed, ~env?, - CCollectionConcat(t, colls), + CCollectionConcat(t, collections), ); let if_ = (~loc=?, ~attributes=?, ~allocation_type, ~env=?, cond, tru, fals) => mk(~loc?, ~attributes?, ~allocation_type, ~env?, CIf(cond, tru, fals)); diff --git a/compiler/src/middle_end/anf_iterator.re b/compiler/src/middle_end/anf_iterator.re index 2876f803d7..f365b2e894 100644 --- a/compiler/src/middle_end/anf_iterator.re +++ b/compiler/src/middle_end/anf_iterator.re @@ -83,7 +83,8 @@ module MakeIter = (Iter: IterArgument) => { | CSetRecordItem(_, record, arg) => iter_imm_expression(record); iter_imm_expression(arg); - | CCollectionConcat(_, colls) => List.iter(iter_imm_expression, colls) + | CCollectionConcat(_, collections) => + List.iter(iter_imm_expression, collections) | CIf(c, t, f) => iter_imm_expression(c); iter_anf_expression(t); diff --git a/compiler/src/middle_end/anf_mapper.re b/compiler/src/middle_end/anf_mapper.re index 35b741f28a..b849688c62 100644 --- a/compiler/src/middle_end/anf_mapper.re +++ b/compiler/src/middle_end/anf_mapper.re @@ -196,9 +196,9 @@ module MakeMap = (Iter: MapArgument) => { process_imm_expression(arg), ), ) - | CCollectionConcat(t, colls) => + | CCollectionConcat(t, collections) => leave_with( - CCollectionConcat(t, List.map(process_imm_expression, colls)), + CCollectionConcat(t, List.map(process_imm_expression, collections)), ) | CIf(cond, t, f) => let cond = process_imm_expression(cond); diff --git a/compiler/src/middle_end/linearize.re b/compiler/src/middle_end/linearize.re index be1acd60c7..1def5f77c5 100644 --- a/compiler/src/middle_end/linearize.re +++ b/compiler/src/middle_end/linearize.re @@ -929,9 +929,10 @@ let rec transl_imm = ), ], ); - | TExpCollectionConcat(t, colls) => + | TExpCollectionConcat(t, collections) => let tmp = gensym("catcollection"); - let (new_args, new_setup) = List.split(List.map(transl_imm, colls)); + let (new_args, new_setup) = + List.split(List.map(transl_imm, collections)); ( Imm.id(~loc, ~env, tmp), List.concat(new_setup) diff --git a/compiler/src/parsing/ast_helper.re b/compiler/src/parsing/ast_helper.re index a1fb050402..c7ede48e09 100644 --- a/compiler/src/parsing/ast_helper.re +++ b/compiler/src/parsing/ast_helper.re @@ -38,6 +38,10 @@ type id = loc(Identifier.t); type str = loc(string); type loc = Location.t; +type array_concat_item = + | ArrayConcatItems(list(expression)) + | ArrayConcatSpread(expression); + let ident_empty = { txt: Identifier.IdentName(Location.mknoloc("[]")), loc: Location.dummy_loc, @@ -433,41 +437,49 @@ module Expression = { tuple_construct(~loc, ~attributes?, ident_cons, [expr, empty]) | ListSpread(expr, _) => expr }; - if (List.exists( - expr => - switch (expr) { - | ListSpread(_) => true - | ListItem(_) => false - }, - rest, - )) { - collection_concat( - ~loc, - ~attributes?, - PExpListConcat, - List.map( - expr => + let has_nonfinal_spread = + List.exists( + expr => + switch (expr) { + | ListSpread(_) => true + | ListItem(_) => false + }, + rest, + ); + if (has_nonfinal_spread) { + let grouped = + List.fold_left( + (acc, expr) => { switch (expr) { - | ListSpread(expr, loc) => ( - PExpSpreadExpr, + | ListSpread(expr, loc) => [ {...expr, pexp_loc: loc}, - ) - | ListItem(expr) => ( - PExpNonSpreadExpr, - // Still convert to a single-element list to make later compilation steps easier + ...acc, + ] + | ListItem(expr) => + let (first, rest) = + switch (acc) { + | [first, ...rest] => (first, rest) + | _ => assert(false) + }; + [ tuple_construct( - ~loc=expr.pexp_loc, + ~loc, ~attributes?, ident_cons, - [ - expr, - tuple_construct(~loc=expr.pexp_loc, ident_empty, []), - ], + [expr, first], ), - ) - }, - a, - ), + ...rest, + ]; + } + }, + [base], + rest, + ); + collection_concat( + ~loc, + ~attributes?, + PExpListConcat, + List.map(expr => (PExpSpreadExpr, expr), grouped), ); } else { List.fold_left( @@ -489,33 +501,38 @@ module Expression = { {...list, pexp_loc: loc}; }; let array_items = (~loc, ~attributes=?, a) => { - let no_spreads = - List.for_all( + let has_spread = + List.exists( x => { switch (x) { - | ArrayItem(_) => true - | ArraySpread(_) => false + | ArraySpread(_) => true + | ArrayItem(_) => false } }, a, ); - if (no_spreads) { - array( - ~loc, - ~attributes?, - List.map( - expr => + if (has_spread) { + let grouped = + List.fold_right( + (expr, acc) => { switch (expr) { - | ArraySpread(_) => - failwith( - "Impossible: spread in array when existence has been disproven", - ) - | ArrayItem(expr) => expr - }, + | ArrayItem(expr) => + switch (acc) { + | [ArrayConcatItems(exprs), ...rest] => [ + ArrayConcatItems([expr, ...exprs]), + ...rest, + ] + | _ => [ArrayConcatItems([expr]), ...acc] + } + | ArraySpread(expr, loc) => [ + ArrayConcatSpread({...expr, pexp_loc: loc}), + ...acc, + ] + } + }, a, - ), - ); - } else { + [], + ); collection_concat( ~loc, ~attributes?, @@ -523,17 +540,31 @@ module Expression = { List.map( x => { switch (x) { - | ArrayItem(expr) => ( + | ArrayConcatItems([first, ...rest] as exprs) => ( PExpNonSpreadExpr, - // Still convert to a single-element array to make later compilation steps easier - array(~loc=expr.pexp_loc, ~attributes?, [expr]), - ) - | ArraySpread(expr, loc) => ( - PExpSpreadExpr, - {...expr, pexp_loc: loc}, + array(~loc=first.pexp_loc, ~attributes?, exprs), ) + | ArrayConcatItems([]) => + failwith("Impossible: empty ArrayConcatItems") + | ArrayConcatSpread(expr) => (PExpSpreadExpr, expr) } }, + grouped, + ), + ); + } else { + array( + ~loc, + ~attributes?, + List.map( + expr => + switch (expr) { + | ArraySpread(_) => + failwith( + "Impossible: spread in array when existence has been disproven", + ) + | ArrayItem(expr) => expr + }, a, ), ); diff --git a/compiler/src/parsing/ast_mapper.re b/compiler/src/parsing/ast_mapper.re index dafa69d8ce..95bf0a2236 100644 --- a/compiler/src/parsing/ast_mapper.re +++ b/compiler/src/parsing/ast_mapper.re @@ -87,12 +87,12 @@ module E = { map_loc(sub, f), sub.expr(sub, v), ) - | PExpCollectionConcat(t, colls) => + | PExpCollectionConcat(t, collections) => collection_concat( ~loc, ~attributes, t, - List.map(((t, expr)) => (t, sub.expr(sub, expr)), colls), + List.map(((t, expr)) => (t, sub.expr(sub, expr)), collections), ) | PExpLet(r, m, vbs) => let_(~loc, ~attributes, r, m, List.map(sub.value_binding(sub), vbs)) diff --git a/compiler/src/typed/typecore.re b/compiler/src/typed/typecore.re index 46860d3ded..a523a58815 100644 --- a/compiler/src/typed/typecore.re +++ b/compiler/src/typed/typecore.re @@ -979,25 +979,28 @@ and type_expect_ = exp_type: Builtin_types.type_void, exp_env: env, }); - | PExpCollectionConcat(t, colls) => + | PExpCollectionConcat(t, collections) => let (mk_builtin_type, texp_type) = switch (t) { | PExpListConcat => (Builtin_types.type_list, TExpListConcat) | PExpArrayConcat => (Builtin_types.type_array, TExpArrayConcat) }; - let coll_type = mk_builtin_type(newvar()); - with_explanation(() => unify_exp_types(loc, env, coll_type, ty_expected)); - let typed_colls = + let collection_type = mk_builtin_type(newvar()); + with_explanation(() => + unify_exp_types(loc, env, collection_type, ty_expected) + ); + let typed_collections = List.map( - ((_, expr)) => type_expect(env, expr, mk_expected(coll_type)), - colls, + ((_, expr)) => + type_expect(env, expr, mk_expected(collection_type)), + collections, ); rue({ - exp_desc: TExpCollectionConcat(texp_type, typed_colls), + exp_desc: TExpCollectionConcat(texp_type, typed_collections), exp_loc: loc, exp_extra: [], exp_attributes: attributes, - exp_type: coll_type, + exp_type: collection_type, exp_env: env, }); | PExpLet(rec_flag, mut_flag, pats) => diff --git a/compiler/src/typed/typedtreeIter.re b/compiler/src/typed/typedtreeIter.re index 1e04205444..e2bb6f70b7 100644 --- a/compiler/src/typed/typedtreeIter.re +++ b/compiler/src/typed/typedtreeIter.re @@ -247,7 +247,8 @@ module MakeIterator = iter_expression(a1); iter_expression(a2); iter_expression(a3); - | TExpCollectionConcat(_, colls) => List.iter(iter_expression, colls) + | TExpCollectionConcat(_, collections) => + List.iter(iter_expression, collections) | TExpIf(c, t, f) => iter_expression(c); iter_expression(t); diff --git a/compiler/src/typed/typedtreeMap.re b/compiler/src/typed/typedtreeMap.re index b402079065..1bd38d7ea1 100644 --- a/compiler/src/typed/typedtreeMap.re +++ b/compiler/src/typed/typedtreeMap.re @@ -249,8 +249,8 @@ module MakeMap = ld, map_expression(arg), ) - | TExpCollectionConcat(t, colls) => - TExpCollectionConcat(t, List.map(map_expression, colls)) + | TExpCollectionConcat(t, collections) => + TExpCollectionConcat(t, List.map(map_expression, collections)) | TExpBlock(args) => TExpBlock(List.map(map_expression, args)) | TExpConstruct(a, b, TExpConstrTuple(args)) => TExpConstruct(a, b, TExpConstrTuple(List.map(map_expression, args))) diff --git a/compiler/test/suites/arrays.re b/compiler/test/suites/arrays.re index 17865152b0..6062d51d15 100644 --- a/compiler/test/suites/arrays.re +++ b/compiler/test/suites/arrays.re @@ -122,8 +122,8 @@ describe("arrays", ({test, testSkip}) => { ); assertRun( "array_spread2", - "let a = [> 1, 2, 3]; print([> 0, ...a])", - "[> 0, 1, 2, 3]\n", + "let a = [> 4, 5]; print([> 1, 2, 3, ...a, 6])", + "[> 1, 2, 3, 4, 5, 6]\n", ); assertRun( "array_spread3", diff --git a/compiler/test/suites/lists.re b/compiler/test/suites/lists.re index 0990bfe8c5..d092ace140 100644 --- a/compiler/test/suites/lists.re +++ b/compiler/test/suites/lists.re @@ -30,8 +30,8 @@ describe("lists", ({test, testSkip}) => { ); assertRun( "list_spread_anywhere2", - "let a = [1, 2]; let b = [4, 5]; print([...a, 3, ...b])", - "[1, 2, 3, 4, 5]\n", + "let a = [2]; let b = [5, 6]; print([1, ...a, 3, 4, ...b])", + "[1, 2, 3, 4, 5, 6]\n", ); assertRun( "list_spread_anywhere3", diff --git a/stdlib/runtime/concat.gr b/stdlib/runtime/concat.gr index 5bfd3988bd..45e430ad5e 100644 --- a/stdlib/runtime/concat.gr +++ b/stdlib/runtime/concat.gr @@ -2,11 +2,16 @@ module Concat include "runtime/unsafe/wasmi32" -from WasmI32 use { (+) } +from WasmI32 use { (<), (>=), (+), (-), (*) } include "runtime/unsafe/memory" include "runtime/dataStructures" from DataStructures use { tagSimpleNumber, allocateArray } +@unsafe +let _ARRAY_LENGTH_OFFSET = 4n +@unsafe +let _ARRAY_DATA_OFFSET = 8n + let rec append = (list1, list2) => { match (list1) { [] => list2, @@ -15,50 +20,43 @@ let rec append = (list1, list2) => { } @unsafe -provide let listConcat = listsArray => { +provide let listConcat = (listsArray: Array>) => { let ptr = WasmI32.fromGrain(listsArray) - let length = WasmI32.load(ptr, 4n) + let length = WasmI32.load(ptr, _ARRAY_LENGTH_OFFSET) + let byteLength = length * 4n let mut result = [] - for (let mut i = 0n; WasmI32.ltU(i, length); i += 1n) { - let list = listsArray[tagSimpleNumber(i)] - result = append(result, list) + for (let mut offset = byteLength - 4n; offset >= 0n; offset -= 4n) { + let listPtr = Memory.incRef(WasmI32.load(ptr + offset, _ARRAY_DATA_OFFSET)) + let list = WasmI32.toGrain(listPtr): List + result = append(list, result) } result } -@unsafe -let findLength = arr => { - let ptr = WasmI32.fromGrain(arr) - WasmI32.load(ptr, 4n) -} - @unsafe provide let arrayConcat = (arraysArray: Array>) => { - let numArrs = findLength(arraysArray) + let ptr = WasmI32.fromGrain(arraysArray) + let numArrsByteLength = WasmI32.load(ptr, _ARRAY_LENGTH_OFFSET) * 4n let mut totLength = 0n - for (let mut i = 0n; WasmI32.ltU(i, numArrs); i += 1n) { - let array = arraysArray[tagSimpleNumber(i)] - totLength += findLength(array) + for (let mut offset = 0n; offset < numArrsByteLength; offset += 4n) { + let arrayPtr = WasmI32.load(ptr + offset, _ARRAY_DATA_OFFSET) + let arrayLen = WasmI32.load(arrayPtr, _ARRAY_LENGTH_OFFSET) + totLength += arrayLen } let result = allocateArray(totLength) - let mut byteOffset = result - for (let mut i = 0n; WasmI32.ltU(i, numArrs); i += 1n) { - let array = arraysArray[tagSimpleNumber(i)] - let length = findLength(array) - for ( - let mut arrI = 0n; - WasmI32.ltU(arrI, length); - arrI += 1n - ) { - WasmI32.store( - byteOffset, - Memory.incRef(WasmI32.fromGrain(array[tagSimpleNumber(arrI)])), - 8n + let mut insertElemOffset = result + for (let mut offset = 0n; offset < numArrsByteLength; offset += 4n) { + let arrayPtr = WasmI32.load(ptr + offset, _ARRAY_DATA_OFFSET) + let arrayByteLen = WasmI32.load(arrayPtr, _ARRAY_LENGTH_OFFSET) * 4n + for (let mut arrOffset = 0n; arrOffset < arrayByteLen; arrOffset += 4n) { + let elem = Memory.incRef( + WasmI32.load(arrayPtr + arrOffset, _ARRAY_DATA_OFFSET) ) - byteOffset += 4n + WasmI32.store(insertElemOffset, elem, _ARRAY_DATA_OFFSET) + insertElemOffset += 4n } } WasmI32.toGrain(result): Array diff --git a/stdlib/runtime/concat.md b/stdlib/runtime/concat.md new file mode 100644 index 0000000000..e35520d039 --- /dev/null +++ b/stdlib/runtime/concat.md @@ -0,0 +1,16 @@ +--- +title: Concat +--- + +### Concat.**listConcat** + +```grain +listConcat : Array> -> List +``` + +### Concat.**arrayConcat** + +```grain +arrayConcat : Array> -> Array +``` + From 732027f181e5b6b6b253ba42afa6f95a5ccc2adf Mon Sep 17 00:00:00 2001 From: Alex Snezhko Date: Mon, 5 Jun 2023 05:35:29 +0200 Subject: [PATCH 3/3] fixed formatting --- compiler/src/formatting/format.re | 90 +++++++++++++--------- compiler/src/parsing/ast_helper.re | 59 ++++++++------ compiler/test/grainfmt/spreads.expected.gr | 1 + compiler/test/grainfmt/spreads.input.gr | 1 + stdlib/runtime/concat.md | 8 +- 5 files changed, 99 insertions(+), 60 deletions(-) diff --git a/compiler/src/formatting/format.re b/compiler/src/formatting/format.re index 89021a204f..d11704b981 100644 --- a/compiler/src/formatting/format.re +++ b/compiler/src/formatting/format.re @@ -2974,9 +2974,53 @@ and print_expression_inner = | PExpCollectionConcat(concat_t, collections) => let list_length = List.length(collections); + let flattened = + List.map( + ((t, e)) => { + let process_elem = + switch (concat_t) { + | PExpListConcat => + let rec process_elem = list => { + switch (list.Parsetree.pexp_desc) { + | Parsetree.PExpConstruct( + {txt: IdentName({txt: "[...]"})}, + PExpConstrTuple([expr, rest]), + ) => + switch (rest.pexp_desc) { + | Parsetree.PExpConstruct( + {txt: IdentName({txt: "[]"})}, + PExpConstrTuple(_), + ) => + // Non-spread element at end of list; do not recurse + [(false, expr)] + | _ => [(false, expr), ...process_elem(rest)] + } + | _ => [(true, list)] + }; + }; + process_elem; + | PExpArrayConcat => ( + array => { + switch (array.Parsetree.pexp_desc) { + | Parsetree.PExpArray(elems) => + List.map(elem => (false, elem), elems) + | _ => assert(false) + }; + } + ) + }; + switch (t) { + | Parsetree.PExpSpreadExpr => [(true, e)] + | Parsetree.PExpNonSpreadExpr => process_elem(e) + }; + }, + collections, + ) + |> List.flatten; + let items = List.mapi( - (index, (t, e)) => { + (index, (is_spread, e)) => { // Do we have any comments on this line? // If so, we break the whole list @@ -3005,24 +3049,12 @@ and print_expression_inner = }; let expr = - switch (t) { - | Parsetree.PExpNonSpreadExpr => - let e = - switch (concat_t, e.pexp_desc) { - | ( - PExpListConcat, - Parsetree.PExpConstruct( - {txt: IdentName({txt: "[...]"})}, - PExpConstrTuple([expr, _]), - ), - ) - | (PExpArrayConcat, Parsetree.PExpArray([expr])) => expr - | _ => - failwith( - "Impossible: formatter: non-spread concat item containing invalid data", - ) - }; - + Doc.concat([ + if (is_spread) { + Doc.text("..."); + } else { + Doc.nil; + }, print_expression( ~expression_parent=GenericExpression, ~original_source, @@ -3032,25 +3064,11 @@ and print_expression_inner = comments, ), e, - ); - | Parsetree.PExpSpreadExpr => - Doc.concat([ - Doc.text("..."), - print_expression( - ~expression_parent=GenericExpression, - ~original_source, - ~comments= - Comment_utils.get_comments_inside_location( - ~location=e.pexp_loc, - comments, - ), - e, - ), - ]) - }; + ), + ]); (expr, end_line_comments); }, - collections, + flattened, ); let (last_line_breaks_for_comments, list_items) = diff --git a/compiler/src/parsing/ast_helper.re b/compiler/src/parsing/ast_helper.re index c7ede48e09..1e783aab40 100644 --- a/compiler/src/parsing/ast_helper.re +++ b/compiler/src/parsing/ast_helper.re @@ -431,12 +431,6 @@ module Expression = { switch (List.rev(a)) { | [] => empty | [base, ...rest] => - let base = - switch (base) { - | ListItem(expr) => - tuple_construct(~loc, ~attributes?, ident_cons, [expr, empty]) - | ListSpread(expr, _) => expr - }; let has_nonfinal_spread = List.exists( expr => @@ -447,29 +441,44 @@ module Expression = { rest, ); if (has_nonfinal_spread) { + let base = + switch (base) { + | ListItem(expr) => ( + PExpNonSpreadExpr, + tuple_construct( + ~loc, + ~attributes?, + ident_cons, + [expr, empty], + ), + ) + | ListSpread(expr, _) => (PExpSpreadExpr, expr) + }; let grouped = List.fold_left( (acc, expr) => { switch (expr) { | ListSpread(expr, loc) => [ - {...expr, pexp_loc: loc}, + (PExpSpreadExpr, {...expr, pexp_loc: loc}), ...acc, ] | ListItem(expr) => - let (first, rest) = - switch (acc) { - | [first, ...rest] => (first, rest) - | _ => assert(false) - }; - [ - tuple_construct( - ~loc, - ~attributes?, - ident_cons, - [expr, first], - ), - ...rest, - ]; + switch (acc) { + | [(_, first), ...rest] => [ + ( + PExpNonSpreadExpr, + tuple_construct( + ~loc, + ~attributes?, + ident_cons, + [expr, first], + ), + ), + ...rest, + ] + | _ => + failwith("Impossible: processed ListItem with empty acc") + } } }, [base], @@ -479,9 +488,15 @@ module Expression = { ~loc, ~attributes?, PExpListConcat, - List.map(expr => (PExpSpreadExpr, expr), grouped), + grouped, ); } else { + let base = + switch (base) { + | ListItem(expr) => + tuple_construct(~loc, ~attributes?, ident_cons, [expr, empty]) + | ListSpread(expr, _) => expr + }; List.fold_left( (acc, expr) => { switch (expr) { diff --git a/compiler/test/grainfmt/spreads.expected.gr b/compiler/test/grainfmt/spreads.expected.gr index de555771ab..1cae126ff4 100644 --- a/compiler/test/grainfmt/spreads.expected.gr +++ b/compiler/test/grainfmt/spreads.expected.gr @@ -39,6 +39,7 @@ let rec reject2 = (fn, list) => { } let l = ["a", "b", "c"] +let a = [...l, "a", "b", ...l, "d"] let a = [ ...l, "secondInAVeryLongString", diff --git a/compiler/test/grainfmt/spreads.input.gr b/compiler/test/grainfmt/spreads.input.gr index 526c876b0a..08a45ffa79 100644 --- a/compiler/test/grainfmt/spreads.input.gr +++ b/compiler/test/grainfmt/spreads.input.gr @@ -33,6 +33,7 @@ let rec reject2 = (fn, list) => { } let l = ["a", "b", "c"] +let a = [...l, "a", "b", ...l, "d"] let a = [...l, "secondInAVeryLongString", "thirdInAStringThatWillBreakLine", ...l] let a = [ diff --git a/stdlib/runtime/concat.md b/stdlib/runtime/concat.md index e35520d039..3b949c99c2 100644 --- a/stdlib/runtime/concat.md +++ b/stdlib/runtime/concat.md @@ -2,15 +2,19 @@ title: Concat --- +## Values + +Functions and constants included in the Concat module. + ### Concat.**listConcat** ```grain -listConcat : Array> -> List +listConcat : (listsArray: Array>) -> List ``` ### Concat.**arrayConcat** ```grain -arrayConcat : Array> -> Array +arrayConcat : (arraysArray: Array>) -> Array ```