Skip to content

Commit

Permalink
dynamic import
Browse files Browse the repository at this point in the history
  • Loading branch information
mununki committed Dec 17, 2022
1 parent e8d2f97 commit 1f9c581
Show file tree
Hide file tree
Showing 22 changed files with 1,024 additions and 813 deletions.
2 changes: 2 additions & 0 deletions jscomp/core/js_packages_info.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ let is_runtime_package (x : t) = x.name = Pkg_runtime

let iter (x : t) cb = Ext_list.iter x.module_systems cb

let map (x : t) cb = Ext_list.map x.module_systems cb

(* let equal (x : t) ({name; module_systems}) =
x.name = name &&
Ext_list.for_all2_no_exn
Expand Down
2 changes: 2 additions & 0 deletions jscomp/core/js_packages_info.mli
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ val same_package_by_name : t -> t -> bool

val iter : t -> (package_info -> unit) -> unit

val map : t -> (package_info -> 'a) -> 'a list

val empty : t

val from_name : string -> t
Expand Down
6 changes: 3 additions & 3 deletions jscomp/core/lam_analysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ let rec no_side_effects (lam : Lam.t) : bool =
| [ _; Lconst cst ] -> not_zero_constant cst
| _ -> false)
| Pcreate_extension _ | Pjs_typeof | Pis_null | Pis_not_none | Psome
| Psome_not_nest | Pis_undefined | Pis_null_undefined | Pnull_to_opt
| Pundefined_to_opt | Pnull_undefined_to_opt | Pjs_fn_make _
| Pjs_object_create _
| Psome_not_nest | Pis_undefined | Pis_null_undefined | Pimport
| Pnull_to_opt | Pundefined_to_opt | Pnull_undefined_to_opt
| Pjs_fn_make _ | Pjs_object_create _
(* TODO: check *)
| Pbytes_to_string | Pmakeblock _
(* whether it's mutable or not *)
Expand Down
255 changes: 129 additions & 126 deletions jscomp/core/lam_compile.ml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions jscomp/core/lam_compile.mli
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
(** Compile single lambda IR to JS IR *)

val compile_recursive_lets :
Lam_compile_context.t -> (Ident.t * Lam.t) list -> Js_output.t
string -> Lam_compile_context.t -> (Ident.t * Lam.t) list -> Js_output.t

val compile_lambda : Lam_compile_context.t -> Lam.t -> Js_output.t
val compile_lambda : string -> Lam_compile_context.t -> Lam.t -> Js_output.t
10 changes: 5 additions & 5 deletions jscomp/core/lam_compile_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
(* module S = Js_stmt_make *)


let compile_group (meta : Lam_stats.t)
let compile_group output_prefix (meta : Lam_stats.t)
(x : Lam_group.t) : Js_output.t =
match x with
(*
Expand All @@ -60,20 +60,20 @@ let compile_group (meta : Lam_stats.t)
(* let lam = Optimizer.simplify_lets [] lam in *)
(* can not apply again, it's wrong USE it with care*)
(* ([Js_stmt_make.comment (Gen_of_env.query_type id env )], None) ++ *)
Lam_compile.compile_lambda { continuation = Declare (kind, id);
Lam_compile.compile_lambda output_prefix { continuation = Declare (kind, id);
jmp_table = Lam_compile_context.empty_handler_map;
meta
} lam

| Recursive id_lams ->
Lam_compile.compile_recursive_lets
Lam_compile.compile_recursive_lets output_prefix
{ continuation = EffectCall Not_tail;
jmp_table = Lam_compile_context.empty_handler_map;
meta
}
id_lams
| Nop lam -> (* TODO: Side effect callls, log and see statistics *)
Lam_compile.compile_lambda {continuation = EffectCall Not_tail;
Lam_compile.compile_lambda output_prefix {continuation = EffectCall Not_tail;
jmp_table = Lam_compile_context.empty_handler_map;
meta
} lam
Expand Down Expand Up @@ -222,7 +222,7 @@ let maybe_pure = no_side_effects groups in
let () = Ext_log.dwarn ~__POS__ "\n@[[TIME:]Pre-compile: %f@]@." (Sys.time () *. 1000.) in
#endif
let body =
Ext_list.map groups (fun group -> compile_group meta group)
Ext_list.map groups (fun group -> compile_group output_prefix meta group)
|> Js_output.concat
|> Js_output.output_as_block
in
Expand Down
63 changes: 60 additions & 3 deletions jscomp/core/lam_compile_primitive.ml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,43 @@ let ensure_value_unit (st : Lam_compile_context.continuation) e : E.t =
| EffectCall Not_tail -> e
(* NeedValue should return a meaningful expression*)

let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t)
(args : J.expression list) : J.expression =
let module_of_expression = function
| J.Var (J.Qualified (module_id, value)) -> [ (module_id, value) ]
| _ -> []

let get_module_system () =
let packages_info = Js_packages_state.get_packages_info () in
let module_systems =
Js_packages_info.map packages_info (fun { module_system } -> module_system)
in
match module_systems with
(* fixme: test mode where the module system is empty *)
| [] -> assert false
| module_system :: _rest -> module_system

let import_of_path path =
E.call
~info:{ arity = Full; call_info = Call_na }
(E.js_global "import")
[ E.str path ]

let wrap_then import value =
let arg = Ident.create "m" in
E.call
~info:{ arity = Full; call_info = Call_na }
(E.dot import "then")
[
E.ocaml_fun ~return_unit:false ~async:false [ arg ]
[
{
statement_desc = J.Return (E.dot (E.var arg) value);
comment = None;
};
];
]

let translate output_prefix loc (cxt : Lam_compile_context.t)
(prim : Lam_primitive.t) (args : J.expression list) : J.expression =
match prim with
| Pis_not_none -> Js_of_lam_option.is_not_none (Ext_list.singleton_exn args)
| Pcreate_extension s -> E.make_exception s
Expand Down Expand Up @@ -78,6 +113,27 @@ let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t)
| _ -> E.runtime_call Js_runtime_modules.option "nullable_to_opt" args
)
| _ -> assert false)
| Pimport -> (
match args with
| [ e ] -> (
let output_dir = Filename.dirname output_prefix in

let module_id, module_value =
match module_of_expression e.expression_desc with
| [ module_ ] -> module_
| _ -> assert false
(* TODO: graceful error message here *)
in

let path =
Js_name_of_module_id.string_of_module_id module_id ~output_dir
(get_module_system ())
in

match module_value with
| Some value -> wrap_then (import_of_path path) value
| None -> import_of_path path)
| _ -> assert false)
| Pjs_function_length -> E.function_length (Ext_list.singleton_exn args)
| Pcaml_obj_length -> E.obj_length (Ext_list.singleton_exn args)
| Pis_null -> E.is_null (Ext_list.singleton_exn args)
Expand Down Expand Up @@ -301,7 +357,8 @@ let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t)
| Backend_type ->
E.make_block E.zero_int_literal
(Blk_constructor { name = "Other"; num_nonconst = 1; tag = 0 })
[ E.str "BS" ] Immutable)
[ E.str "BS" ]
Immutable)
| Pduprecord -> Lam_dispatch_primitive.translate loc "?obj_dup" args
| Plazyforce
(* FIXME: we don't inline lazy force or at least
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/lam_compile_primitive.mli
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*)

val translate :
string ->
Location.t ->
Lam_compile_context.t ->
Lam_primitive.t ->
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/lam_convert.ml
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) :
| "#nullable_to_opt" -> Pnull_undefined_to_opt
| "#null_to_opt" -> Pnull_to_opt
| "#is_nullable" -> Pis_null_undefined
| "#import" ->Pimport
| "#string_append" -> Pstringadd
| "#wrap_exn" -> Pwrap_exn
| "#obj_length" -> Pcaml_obj_length
Expand Down
2 changes: 2 additions & 0 deletions jscomp/core/lam_primitive.ml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ type t =
| Pis_null
| Pis_undefined
| Pis_null_undefined
| Pimport
| Pjs_typeof
| Pjs_function_length
| Pcaml_obj_length
Expand Down Expand Up @@ -218,6 +219,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) =
| Psome_not_nest -> rhs = Psome_not_nest
| Pis_undefined -> rhs = Pis_undefined
| Pis_null_undefined -> rhs = Pis_null_undefined
| Pimport -> rhs = Pimport
| Pjs_typeof -> rhs = Pjs_typeof
| Pisint -> rhs = Pisint
| Pis_poly_var_block -> rhs = Pis_poly_var_block
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/lam_primitive.mli
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ type t =
| Pis_null
| Pis_undefined
| Pis_null_undefined
| Pimport
| Pjs_typeof
| Pjs_function_length
| Pcaml_obj_length
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/lam_print.ml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ let primitive ppf (prim : Lam_primitive.t) =
| Pval_from_option_not_nest -> fprintf ppf "[?unbox-not-nest]"
| Pis_undefined -> fprintf ppf "[?undefined]"
| Pis_null_undefined -> fprintf ppf "[?null?undefined]"
| Pimport -> fprintf ppf "[import]"
| Pmakeblock (tag, _, Immutable) -> fprintf ppf "makeblock %i" tag
| Pmakeblock (tag, _, Mutable) -> fprintf ppf "makemutable %i" tag
| Pfield (n, field_info) -> (
Expand Down
24 changes: 24 additions & 0 deletions jscomp/frontend/ast_await.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,27 @@ let create_await_expression (e : Parsetree.expression) =
{ txt = Ldot (Ldot (Lident "Js", "Promise"), "unsafe_await"); loc }
in
Ast_helper.Exp.apply ~loc unsafe_await [ (Nolabel, e) ]

let create_await_module_expression ~module_type_name (e : Parsetree.module_expr)
=
let open Ast_helper in
{
e with
pmod_desc =
Pmod_unpack
(create_await_expression
(Exp.apply
(Exp.ident ~loc:e.pmod_loc
{
txt = Longident.Ldot (Lident "Js", "import");
loc = e.pmod_loc;
})
[
( Nolabel,
Exp.constraint_ ~loc:e.pmod_loc
(Exp.pack ~loc:e.pmod_loc e)
(Typ.package ~loc:e.pmod_loc
{ txt = Lident module_type_name; loc = e.pmod_loc }
[]) );
]));
}
44 changes: 41 additions & 3 deletions jscomp/frontend/bs_builtin_ppx.ml
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ let expr_mapper ~async_context ~in_function_def (self : mapper)
match Ast_attributes.has_await_payload e.pexp_attributes with
| None -> result
| Some _ ->
if !async_context = false then
Location.raise_errorf ~loc:e.pexp_loc
"Await on expression not in an async context";
(* if !async_context = false then
Location.raise_errorf ~loc:e.pexp_loc
"Await on expression not in an async context"; *)
Ast_await.create_await_expression result

let typ_mapper (self : mapper) (typ : Parsetree.core_type) =
Expand Down Expand Up @@ -434,6 +434,13 @@ let local_module_name =
incr v;
"local_" ^ string_of_int !v

let local_module_type_name =
let v = ref 0 in
fun ({ txt } : Longident.t Location.loc) ->
incr v;
(Longident.flatten txt |> List.fold_left (fun ll l -> ll ^ l) "")
^ string_of_int !v

let expand_reverse (stru : Ast_structure.t) (acc : Ast_structure.t) :
Ast_structure.t =
if stru = [] then acc
Expand Down Expand Up @@ -497,6 +504,37 @@ let rec structure_mapper (self : mapper) (stru : Ast_structure.t) =
| _ -> expand_reverse acc (structure_mapper self rest)
in
aux [] stru
| Pstr_module
({
pmb_expr =
{ pmod_desc = Pmod_ident { txt; loc }; pmod_attributes } as me;
} as mb)
(* module M = @res.await Belt.List *)
when Res_parsetree_viewer.hasAwaitAttribute pmod_attributes ->
let item = self.structure_item self item in
let safe_module_type_name = local_module_type_name { txt; loc } in
let module_type_decl =
let open Ast_helper in
Str.modtype ~loc
(Mtd.mk ~loc
{ txt = safe_module_type_name; loc }
~typ:(Mty.typeof_ ~loc me))
in
(* module BeltList0 = module type of Belt.List *)
module_type_decl
:: {
item with
pstr_desc =
Pstr_module
{
mb with
pmb_expr =
Ast_await.create_await_module_expression
~module_type_name:safe_module_type_name mb.pmb_expr;
};
}
(* module M = @res.await Belt.List *)
:: structure_mapper self rest
| _ -> self.structure_item self item :: structure_mapper self rest)

let mapper : mapper =
Expand Down
1 change: 1 addition & 0 deletions jscomp/others/js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ external toOption : 'a nullable -> 'a option = "#nullable_to_opt"
external undefinedToOption : 'a undefined -> 'a option = "#undefined_to_opt"
external nullToOption : 'a null -> 'a option = "#null_to_opt"
external isNullable : 'a nullable -> bool = "#is_nullable"
external import : 'a -> 'a promise = "#import"

external testAny : 'a -> bool = "#is_nullable"
(** The same as {!test} except that it is more permissive on the types of input *)
Expand Down
2 changes: 2 additions & 0 deletions jscomp/runtime/js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ external nullToOption : 'a null -> 'a option = "#null_to_opt"

external isNullable : 'a nullable -> bool = "#is_nullable"

external import : 'a -> 'a promise = "#import"

(** The same as {!test} except that it is more permissive on the types of input *)
external testAny : 'a -> bool = "#is_nullable"

Expand Down
58 changes: 58 additions & 0 deletions jscomp/test/Import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

var Curry = require("../../lib/js/curry.js");

async function eachIntAsync(list, f) {
return Curry._2(await import("../../lib/js/belt_List.js").then(function (m) {
return m.forEach;
}), list, f);
}

function eachIntLazy(list, f) {
var obj = import("../../lib/js/belt_List.js").then(function (m) {
return m.forEach;
});
var arg1 = function (each) {
return Promise.resolve(Curry._2(each, list, f));
};
return obj.then(arg1);
}

eachIntLazy({
hd: 1,
tl: {
hd: 2,
tl: {
hd: 3,
tl: /* [] */0
}
}
}, (function (n) {
console.log("lazy", n);
}));

eachIntAsync({
hd: 1,
tl: {
hd: 2,
tl: {
hd: 3,
tl: /* [] */0
}
}
}, (function (n) {
console.log("async", n);
}));

var beltAsModule = import("../../lib/js/belt_List.js");

var M = await import("../../lib/js/belt_List.js");

var each = M.forEach;

exports.eachIntAsync = eachIntAsync;
exports.eachIntLazy = eachIntLazy;
exports.beltAsModule = beltAsModule;
exports.M = M;
exports.each = each;
/* Not a pure module */
Loading

0 comments on commit 1f9c581

Please sign in to comment.