diff --git a/compiler/src/codegen/compcore.re b/compiler/src/codegen/compcore.re index 7a4ba26cd8..fa730204ab 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, 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, [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/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..4ca4f12fae 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, collections) => + 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..d11704b981 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,133 @@ and print_expression_inner = print_ident(txt), ]); print_assignment(~original_source, ~comments, left, expression2); + | 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, (is_spread, 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(collections, 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 = + Doc.concat([ + if (is_spread) { + Doc.text("..."); + } else { + Doc.nil; + }, + print_expression( + ~expression_parent=GenericExpression, + ~original_source, + ~comments= + Comment_utils.get_comments_inside_location( + ~location=e.pexp_loc, + comments, + ), + e, + ), + ]); + (expr, end_line_comments); + }, + flattened, + ); + + 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..f4fdc11049 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, collections) => + List.fold_left( + (acc, a) => Ident.Set.union(imm_free_vars(a), acc), + Ident.Set.empty, + collections, + ) | 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..ec9431ecf6 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, collections) => + mk( + ~loc?, + ~attributes?, + ~allocation_type=Managed, + ~env?, + CCollectionConcat(t, collections), + ); 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..f365b2e894 100644 --- a/compiler/src/middle_end/anf_iterator.re +++ b/compiler/src/middle_end/anf_iterator.re @@ -83,6 +83,8 @@ module MakeIter = (Iter: IterArgument) => { | CSetRecordItem(_, record, arg) => iter_imm_expression(record); iter_imm_expression(arg); + | 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 469d4c1852..b849688c62 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, collections) => + leave_with( + CCollectionConcat(t, List.map(process_imm_expression, collections)), + ) | 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..1def5f77c5 100644 --- a/compiler/src/middle_end/linearize.re +++ b/compiler/src/middle_end/linearize.re @@ -929,6 +929,21 @@ let rec transl_imm = ), ], ); + | TExpCollectionConcat(t, collections) => + let tmp = gensym("catcollection"); + let (new_args, new_setup) = + List.split(List.map(transl_imm, collections)); + ( + 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..1e783aab40 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); @@ -34,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, @@ -415,37 +423,167 @@ 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 = switch (List.rev(a)) { | [] => empty | [base, ...rest] => - let base = - switch (base) { - | ListItem(expr) => - tuple_construct(~loc, ~attributes?, ident_cons, [expr, empty]) - | ListSpread(expr, _) => expr - }; - List.fold_left( - (acc, 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.", + let has_nonfinal_spread = + List.exists( + expr => + switch (expr) { + | ListSpread(_) => true + | ListItem(_) => false + }, + 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) => [ + (PExpSpreadExpr, {...expr, pexp_loc: loc}), + ...acc, + ] + | ListItem(expr) => + switch (acc) { + | [(_, first), ...rest] => [ + ( + PExpNonSpreadExpr, + tuple_construct( + ~loc, + ~attributes?, + ident_cons, + [expr, first], + ), + ), + ...rest, + ] + | _ => + failwith("Impossible: processed ListItem with empty acc") + } + } + }, + [base], + rest, + ); + collection_concat( + ~loc, + ~attributes?, + PExpListConcat, + 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) { + | 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 has_spread = + List.exists( + x => { + switch (x) { + | ArraySpread(_) => true + | ArrayItem(_) => false + } + }, + a, + ); + if (has_spread) { + let grouped = + List.fold_right( + (expr, acc) => { + switch (expr) { + | ArrayItem(expr) => + switch (acc) { + | [ArrayConcatItems(exprs), ...rest] => [ + ArrayConcatItems([expr, ...exprs]), + ...rest, + ] + | _ => [ArrayConcatItems([expr]), ...acc] + } + | ArraySpread(expr, loc) => [ + ArrayConcatSpread({...expr, pexp_loc: loc}), + ...acc, + ] } }, - base, - rest, + a, + [], ); - }; - {...list, pexp_loc: loc}; + collection_concat( + ~loc, + ~attributes?, + PExpArrayConcat, + List.map( + x => { + switch (x) { + | ArrayConcatItems([first, ...rest] as exprs) => ( + PExpNonSpreadExpr, + 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, + ), + ); + }; }; 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..95bf0a2236 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, collections) => + collection_concat( + ~loc, + ~attributes, + t, + 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)) | 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..a523a58815 100644 --- a/compiler/src/typed/typecore.re +++ b/compiler/src/typed/typecore.re @@ -979,6 +979,30 @@ and type_expect_ = exp_type: Builtin_types.type_void, exp_env: env, }); + | PExpCollectionConcat(t, collections) => + let (mk_builtin_type, texp_type) = + switch (t) { + | PExpListConcat => (Builtin_types.type_list, TExpListConcat) + | PExpArrayConcat => (Builtin_types.type_array, TExpArrayConcat) + }; + 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(collection_type)), + collections, + ); + rue({ + exp_desc: TExpCollectionConcat(texp_type, typed_collections), + exp_loc: loc, + exp_extra: [], + exp_attributes: attributes, + exp_type: collection_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..e2bb6f70b7 100644 --- a/compiler/src/typed/typedtreeIter.re +++ b/compiler/src/typed/typedtreeIter.re @@ -247,6 +247,8 @@ module MakeIterator = iter_expression(a1); iter_expression(a2); iter_expression(a3); + | 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 d2dad743a4..1bd38d7ea1 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, 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/grainfmt/spreads.expected.gr b/compiler/test/grainfmt/spreads.expected.gr index ee4112842f..1cae126ff4 100644 --- a/compiler/test/grainfmt/spreads.expected.gr +++ b/compiler/test/grainfmt/spreads.expected.gr @@ -37,3 +37,28 @@ 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 = [ + ...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..08a45ffa79 100644 --- a/compiler/test/grainfmt/spreads.input.gr +++ b/compiler/test/grainfmt/spreads.input.gr @@ -31,3 +31,22 @@ 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 = [ +...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..6062d51d15 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 = [> 4, 5]; print([> 1, 2, 3, ...a, 6])", + "[> 1, 2, 3, 4, 5, 6]\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..d092ace140 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 = [2]; let b = [5, 6]; print([1, ...a, 3, 4, ...b])", + "[1, 2, 3, 4, 5, 6]\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..45e430ad5e --- /dev/null +++ b/stdlib/runtime/concat.gr @@ -0,0 +1,63 @@ +/* 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 } + +@unsafe +let _ARRAY_LENGTH_OFFSET = 4n +@unsafe +let _ARRAY_DATA_OFFSET = 8n + +let rec append = (list1, list2) => { + match (list1) { + [] => list2, + [first, ...rest] => [first, ...append(rest, list2)], + } +} + +@unsafe +provide let listConcat = (listsArray: Array>) => { + let ptr = WasmI32.fromGrain(listsArray) + let length = WasmI32.load(ptr, _ARRAY_LENGTH_OFFSET) + let byteLength = length * 4n + let mut result = [] + + 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 +provide let arrayConcat = (arraysArray: Array>) => { + let ptr = WasmI32.fromGrain(arraysArray) + let numArrsByteLength = WasmI32.load(ptr, _ARRAY_LENGTH_OFFSET) * 4n + + let mut totLength = 0n + 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 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) + ) + 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..3b949c99c2 --- /dev/null +++ b/stdlib/runtime/concat.md @@ -0,0 +1,20 @@ +--- +title: Concat +--- + +## Values + +Functions and constants included in the Concat module. + +### Concat.**listConcat** + +```grain +listConcat : (listsArray: Array>) -> List +``` + +### Concat.**arrayConcat** + +```grain +arrayConcat : (arraysArray: Array>) -> Array +``` +