diff --git a/compiler/opa2js/_tags b/compiler/opa2js/_tags
new file mode 100644
index 00000000..dfea5823
--- /dev/null
+++ b/compiler/opa2js/_tags
@@ -0,0 +1,9 @@
+# -*- conf -*- (for emacs)
+
+<**/*.{ml,mli}>: use_buildinfos, use_libbase, use_compilerlib, use_passlib
+
+<**/*.native>: thread, use_dynlink, use_graph, use_str, use_cryptokit, use_unix, use_nums, use_zip, use_buildinfos, use_libbase, use_ulex, use_libtrx, use_libqmlcompil, use_libbsl, use_opalib, use_opalang, use_opapasses, use_qmlfakecompiler, use_qmlflatcompiler, use_qml2ocaml, use_qmljsimp, use_qml2js, use_opabsl_for_compiler, use_qmlslicer, use_jslang, use_qmlcpsrewriter, use_ocamllang, use_passlib, use_compilerlib, use_pplib, use_qmlpasses, use_opacapi, use_libopa
+
+<**/opx2jsPasses.{ml,mli}>: use_jslang, use_opalib, use_opalang, use_opapasses, use_libopa, use_libqmlcompil, use_qmlpasses, use_qmlcpsrewriter, use_qmlslicer
+
+<**/pass_NodeJsPluginCompilation.{ml,mli}>: use_jslang, use_libqmlcompil, use_qmljsimp
diff --git a/compiler/opa2js/opx2js.ml b/compiler/opa2js/opx2js.ml
new file mode 100644
index 00000000..752d2c17
--- /dev/null
+++ b/compiler/opa2js/opx2js.ml
@@ -0,0 +1,60 @@
+(*
+ Copyright © 2011, 2012 MLstate
+
+ This file is part of Opa.
+
+ Opa is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License, version 3, as published by
+ the Free Software Foundation.
+
+ Opa is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Opa. If not, see .
+*)
+
+(* Opening the generic pass system. *)
+module PH = PassHandler
+
+(* FIXME: define a module InfixOperators in PassHandler *)
+(* this could by the only case an 'open' is allowed *)
+let (|+>) = PH.(|+>)
+let (|>) = PH.(|>)
+let (>) = PH.(>)
+let (&) = PH.(&)
+let (|?>) = PH.(|?>)
+let (|?|) = PH.(|?|)
+let (or) = PH.(or)
+
+(* Set title of generic pass system. *)
+let _ = PH.set_title "Opa2JS"
+
+module O2J = Opx2jsPasses
+
+(* Run all passes *)
+let code =
+ (**********************************************)
+ (* INITIALIZATION *****************************)
+ PH.init
+
+ |+> ("Welcome", O2J.pass_Welcome)
+
+ |+> ("CheckOptions", O2J.pass_CheckOptions)
+
+ |+> ("LoadEnvironment", O2J.pass_LoadEnvironment (fun e -> e
+
+ |+> ("NodeJsPluginCompilation", O2J.pass_NodeJsPluginCompilation)
+
+ |+> ("NodeJsPluginGeneration", O2J.pass_NodeJsPluginGeneration)
+
+ |> PH.return
+ ))
+
+ |> PH.return
+
+
+let () = OManager.exit code
+
diff --git a/compiler/opa2js/opx2jsOptions.ml b/compiler/opa2js/opx2jsOptions.ml
new file mode 100644
index 00000000..77a825b3
--- /dev/null
+++ b/compiler/opa2js/opx2jsOptions.ml
@@ -0,0 +1,69 @@
+(*
+ Copyright © 2011, 2012 MLstate
+
+ This file is part of Opa.
+
+ Opa is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License, version 3, as published by
+ the Free Software Foundation.
+
+ Opa is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Opa. If not, see .
+*)
+
+module Arg = Base.Arg
+module Format = BaseFormat
+module String = BaseString
+
+type t = {
+ build_dir : string;
+ extra_path : string list;
+ packages : string list;
+}
+
+let default_options = {
+ packages = [];
+ build_dir = Sys.getcwd ();
+ extra_path = [];
+}
+
+let parsed = ref false
+
+let r = ref default_options
+
+let add_packages f =
+ r := {!r with packages = f::!r.packages}
+
+let add_extra_path f =
+ r := {!r with extra_path = f::!r.extra_path}
+
+let set_build_dir build_dir = r := {!r with build_dir}
+
+let options = [
+ "--build-dir", Arg.String set_build_dir,
+ "Set the build directory";
+
+ "-I", Arg.String add_extra_path,
+ "Add the given directory to the list of directories searched";
+
+] @ OManager.Arg.options
+ @ ObjectFiles.Arg.public_options
+
+let anon_fun arg = add_packages arg
+
+let get_options () =
+ if not !parsed then (
+ Arg.parse options anon_fun "";
+ parsed := true;
+ ObjectFiles.set_relative_stdlib "stdlib.qmljs";
+ ObjectFiles.set_extrapaths ~no_stdlib:false !r.extra_path;
+ );
+ !r
+
+let print_help () =
+ Arg.usage options ""
diff --git a/compiler/opa2js/opx2jsPasses.ml b/compiler/opa2js/opx2jsPasses.ml
new file mode 100644
index 00000000..80eec4dd
--- /dev/null
+++ b/compiler/opa2js/opx2jsPasses.ml
@@ -0,0 +1,128 @@
+(*
+ Copyright © 2012 MLstate
+
+ This file is part of Opa.
+
+ Opa is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License, version 3, as published by
+ the Free Software Foundation.
+
+ Opa is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Opa. If not, see .
+*)
+
+module List = BaseList
+
+module PH = PassHandler
+module O = Opx2jsOptions
+
+type options = Opx2jsOptions.t
+
+type ('env, 'env2) pass = (options, options, 'env, 'env2) PassHandler.pass
+
+let pass_Welcome =
+ PassHandler.make_pass
+ (fun {PH.env=()} ->
+ let options = Opx2jsOptions.get_options () in
+ OManager.verbose "Opa version %s" BuildInfos.opa_version_name ;
+ OManager.verbose "(c) 2007-%s MLstate, All Rights Reserved." BuildInfos.year;
+ OManager.verbose "Build: %s" BuildInfos.version_id;
+ PassHandler.make_env options ())
+
+let pass_CheckOptions =
+ PassHandler.make_pass
+ (fun e ->
+ if List.is_empty e.PH.options.O.packages
+ then (
+ OManager.printf "@{No packages is specify@}@.";
+ O.print_help ();
+ OManager.printf "@[<2>@{Hint@}:@\nprecise some packages@]@.";
+ exit 1;
+ ) else e
+ )
+
+type env = {
+ package : ObjectFiles.package;
+ renaming : SurfaceAstRenaming.SExpr.t;
+ gamma : QmlTypes.Env.t;
+ undot : QmlAst.expr StringMap.t IdentMap.t;
+ skipped : Ident.t IdentMap.t;
+ code : JsAst.code
+}
+
+let pass_LoadEnvironment k =
+ PassHandler.make_pass
+ (fun e ->
+ let options = e.PH.options in
+ let module RawRenaming = ObjectFiles.MakeRaw(SurfaceAstRenaming.SExpr) in
+ let module RawTyping = ObjectFiles.MakeRaw(Pass_Typing.S) in
+ let module RawTypeDefinition = ObjectFiles.MakeRaw(Pass_TypeDefinition.S) in
+ let module RawUndot = ObjectFiles.MakeRaw(Pass_Undot.S) in
+ let _ = List.fold_left
+ (fun _acc package_name ->
+ let package = package_name, FilePos.nopos "commandLine" in
+ let renaming = RawRenaming.load1 package in
+ let gamma = RawTypeDefinition.load1 package in
+ let gamma = QmlTypes.Env.Ident.from_map (RawTyping.load1 package) gamma in
+ let srenaming = QmlSimpleSlicer.get_renaming package ~side:`server in
+ let undot =
+ let {Pass_Undot. modules; aliases} = (fst (RawUndot.load1 package)) in
+ IdentMap.fold (fun a i modules -> IdentMap.add a (IdentMap.find i modules) modules)
+ aliases modules
+ in
+ let skipped =
+ IdentMap.fold
+ (fun cps skip acc ->
+ IdentMap.add (IdentMap.find cps srenaming) skip acc)
+ (QmlCpsRewriter.get_skipped package)
+ IdentMap.empty
+ in
+ Format.eprintf "package %s@\nRenaming@[%a@]@\nGamma@[%a@]@\n%!"
+ package_name
+ (StringMap.pp ",@ "
+ (fun fmt k (i, _) ->
+ Format.fprintf fmt "%s => %a@\n" k OpaPrint.ident#ident i
+ )
+ ) renaming
+ QmlTypes.Env.pp gamma
+ ;
+ k (PassHandler.make_env options
+ {renaming; gamma; undot; skipped; package; code=[]})
+ ) 0 options.O.packages
+ in
+ PassHandler.make_env options 0
+ )
+
+
+let pass_NodeJsPluginCompilation =
+ PassHandler.make_pass
+ (fun e ->
+ let options = e.PH.options in
+ let {renaming; gamma; undot; skipped; package} = e.PH.env in
+ let env = Pass_NodeJsPluginCompilation.build_env
+ ~package ~renaming ~gamma ~undot ~skipped ~ei:()
+ in
+ let code = Pass_NodeJsPluginCompilation.process env in
+ PassHandler.make_env options {e.PH.env with code}
+ )
+
+
+let pass_NodeJsPluginGeneration =
+ PassHandler.make_pass
+ (fun e ->
+ let options = e.PH.options in
+ let {code; package} = e.PH.env in
+ let directory = Filename.concat options.O.build_dir (fst package) in
+ if not(File.check_create_path directory) then
+ OManager.error "cannot create directory '%s'" directory;
+ let jsfile = Filename.concat directory "main.js" in
+ match File.pp_output jsfile JsPrint.debug_pp#code code with
+ | None -> PassHandler.make_env options 0
+ | Some msg -> OManager.error "%s" msg
+ )
+
diff --git a/compiler/opa2js/opx2jsPasses.mli b/compiler/opa2js/opx2jsPasses.mli
new file mode 100644
index 00000000..38dc348b
--- /dev/null
+++ b/compiler/opa2js/opx2jsPasses.mli
@@ -0,0 +1,15 @@
+type options = Opx2jsOptions.t
+
+type ('env, 'env2) pass = (options, options, 'env, 'env2) PassHandler.pass
+
+val pass_Welcome : (unit, options, unit, unit) PassHandler.pass
+
+val pass_CheckOptions : (unit, unit) pass
+
+type env
+
+val pass_LoadEnvironment : ((options, env) PassHandler.one_env -> int) -> (unit, int) pass
+
+val pass_NodeJsPluginCompilation : (env, env) pass
+
+val pass_NodeJsPluginGeneration : (env, int) pass
diff --git a/compiler/opa2js/pass_NodeJsPluginCompilation.ml b/compiler/opa2js/pass_NodeJsPluginCompilation.ml
new file mode 100644
index 00000000..69d2d471
--- /dev/null
+++ b/compiler/opa2js/pass_NodeJsPluginCompilation.ml
@@ -0,0 +1,338 @@
+(*
+ Copyright © 2012 MLstate
+
+ This file is part of Opa.
+
+ Opa is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License, version 3, as published by
+ the Free Software Foundation.
+
+ Opa is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Opa. If not, see .
+*)
+
+module List = BaseList
+module Format = BaseFormat
+
+module Q = QmlAst
+
+module J = JsAst
+module C = JsCons
+module CE = C.Expr
+module CS = C.Statement
+module CI = C.Ident
+
+type info = {
+ cps : [
+ | `skipped of Ident.t * Ident.t
+ | `unskipped of Ident.t
+ ];
+ type_ : QmlAst.ty;
+ ei : bool;
+ fields : infos;
+}
+
+and infos = info StringMap.t
+
+type env = {
+ infos : infos;
+ gamma : QmlTypes.Env.t;
+ package : ObjectFiles.package;
+}
+
+let pp_info fmt info = Format.fprintf fmt "{@[cps: %a;@]@\n@[type_: %a;@]@\n@[ei: %b;@]@\n@[fields: %a;@]}"
+ (fun fmt -> function
+ | `skipped (skip, cps) ->
+ Format.fprintf fmt "skipped(%a, %a)"
+ QmlPrint.pp#ident skip
+ QmlPrint.pp#ident cps
+ | `unskipped (cps) ->
+ Format.fprintf fmt "unskipped(%a)"
+ QmlPrint.pp#ident cps)
+ info.cps
+ QmlPrint.pp#ty info.type_
+ info.ei
+ (StringMap.pp ", " (fun fmt k _ -> Format.fprintf fmt "%s" k)) info.fields
+
+let build_env ~package ~renaming ~gamma ~undot ~skipped ~ei =
+ ignore ei;
+ let add_value path ident type_ fields env =
+ let cps =
+ try
+ `skipped (IdentMap.find ident skipped, ident)
+ with Not_found -> `unskipped ident
+ in
+ let info = {cps; type_; ei = false; fields} in
+ StringMap.add path info env
+ in
+ let infos =
+ StringMap.fold
+ (fun s (i, _) env ->
+ let rec aux path ident env =
+ let tsc = QmlTypes.Env.Ident.find ident gamma in
+ let oty = QmlTypes.Scheme.instantiate tsc in
+ let ity = QmlTypesUtils.Inspect.follow_alias_noopt gamma oty in
+ let fields =
+ match ity with
+ | Q.TypeRecord Q.TyRow (fields, _rvar) ->
+ begin try
+ let fmap = IdentMap.find ident undot in
+ List.fold_left
+ (fun env (f, _type_) ->
+ match StringMap.find f fmap with
+ | Q.Ident (_, ident) ->
+ aux f ident env
+ | _ -> assert false
+ ) StringMap.empty fields
+ with Not_found -> StringMap.empty
+ end
+ | _ -> StringMap.empty
+ in
+ add_value path ident oty fields env
+ in
+ aux s i env
+ ) renaming StringMap.empty
+ in {infos; gamma; package}
+
+let genid level i =
+ CI.native (
+ if level = 0 then IdentGenerator.alphanum i
+ else Printf.sprintf "%s%i" (IdentGenerator.alphanum i) level
+ )
+
+let rev_way = function
+ | `opa2js c -> `js2opa c
+ | `js2opa c -> `opa2js c
+
+let get_tmp_var, flush_tmp_var =
+ let rvars = ref (0, []) in
+ (fun () ->
+ let nb, vars = !rvars in
+ let newv = CI.native (Printf.sprintf "tmp%i" nb) in
+ rvars := (nb+1, newv::vars);
+ newv),
+ (fun () ->
+ let _, vars = !rvars in
+ rvars := (0, []);
+ vars)
+
+(* TODO: No direct call to "cont", we should really resolve the cont_native
+ bypass*)
+let rec cont_native level gamma k (ret:QmlAst.ty) =
+ let k =
+ let kparam = CI.native "r" in
+ let proj, e = project level gamma (CE.ident kparam) ret (`opa2js `cps) in
+ if proj then
+ CE.function_ None [kparam] [CS.return (CE.call (CE.ident k) [e])]
+ else (CE.ident k)
+ in CE.call (CE.native_global "cont") [k]
+
+
+(* Project an Opa continuation to a JavaScript callback
+ function(r){return_(k, r)} *)
+and uncont_native level gamma k (ret:QmlAst.ty) =
+ let rpar = CI.native "r" in
+ let rarg =
+ match project level gamma (CE.ident rpar) ret (`js2opa `cps) with
+ | false, _ -> CE.ident rpar
+ | true, e -> e
+ in
+ CE.function_ None [rpar]
+ [CS.expr (CE.call (CE.native_global "return_") [(CE.ident k); rarg])]
+
+and project_lambda_args level gamma args way =
+ List.fold_left
+ (fun (i, proj, params, args) t ->
+ let ident = genid level i in
+ let expr = CE.ident ident in
+ let p, expr = project (level+1) gamma expr t (rev_way way) in
+ i+1, p || proj, ident :: params, expr :: args
+ ) (0, false, [], []) args
+
+and project_lambda level gamma args (ret:QmlAst.ty) expr way =
+ let nb, proj, rparams, rargs = project_lambda_args level gamma args way in
+ match way with
+ | `opa2js `cps ->
+ (* function(..., f){return expr(..., cont_native(f))} *)
+ let f = genid level (nb+1) in
+ let params = List.rev (f::rparams) in
+ let args = List.rev ((cont_native level gamma f ret)::rargs) in
+ true,
+ CE.function_ None params [CS.return (CE.call expr args)]
+
+ | `js2opa `cps ->
+ (* expr.length == arity
+ ? function(..., k){return_(k, expr(...))}
+ : function(..., k){return expr(..., function(r){return_(k, r)})}
+ *)
+ let k = genid level (nb+1) in
+ let params = List.rev (k::rparams) in
+ let cpsjs =
+ let args = List.rev ((uncont_native level gamma k ret)::rargs) in
+ CE.function_ None params [CS.return (CE.call expr args)]
+ in
+ let nocpsjs =
+ let args = List.rev rargs in
+ let _, expr = project (level+1) gamma (CE.call expr args) ret (`js2opa `no) in
+ CE.function_ None params [
+ CS.expr (CE.call (CE.native_global "return_") [(CE.ident k); expr])
+ ]
+ in
+ true,
+ CE.cond
+ (CE.equality (CE.dot expr "length") (CE.int (List.length args)))
+ nocpsjs
+ cpsjs
+
+ | `opa2js `no
+ | `js2opa `no ->
+ (* function(...){return proj(expr(...))} *)
+ let p, ret = project (level+1) gamma
+ (CE.call ~pure:false expr (List.rev rargs)) ret
+ (rev_way way)
+ in
+ let proj = p || proj in
+ proj,
+ if proj then CE.function_ None (List.rev rparams) [CS.return ret]
+ else expr
+
+and project_option level gamma expr ty way =
+ true,
+ match way with
+ | `opa2js _ ->
+ let expr = CE.call Imp_Common.ClientLib.udot [expr; CE.string "some"] in
+ snd (project level gamma expr ty way)
+ | `js2opa _ ->
+ let tmp = get_tmp_var () in
+ let _, expr = project level gamma expr ty way in
+ CE.cond (CE.equality (CE.assign (CE.ident tmp) expr) (CE.undefined ()))
+ (CE.obj ["none", CE.obj []])
+ (CE.obj ["some", (CE.ident tmp)])
+
+and project level gamma expr (ty:QmlAst.ty) way =
+ Format.eprintf "Projection of %a\n%!" QmlPrint.pp#ty ty;
+ match ty with
+ | Q.TypeConst _
+ | Q.TypeVar _
+ | Q.TypeAbstract _ -> false, expr
+ | Q.TypeArrow (args, ret) -> project_lambda level gamma args ret expr way
+ | Q.TypeRecord Q.TyRow (fields, _rvar) ->
+ let fields, proj = List.fold_right_map
+ (fun (s, t) proj ->
+ let p, expr = project (level+1) gamma (CE.dot expr s) t way in
+ (s, expr), p || proj
+ ) fields false
+ in
+ proj, (if proj then CE.obj fields else expr)
+ | Q.TypeName (args, ident) ->
+ begin match args, Ident.original_name ident with
+ | [p], "option" ->
+ project_option level gamma expr p way
+ | _ ->
+ project level gamma expr
+ (QmlTypesUtils.Inspect.find_and_specialize gamma ident args)
+ way
+ end
+ | Q.TypeForall (_vars, _rvars, _cvars, ty) ->
+ project level gamma expr ty way
+
+ | Q.TypeSum _
+ | Q.TypeSumSugar _ as ty ->
+ Format.eprintf "FIXME NO PROJECTION %a\n%!" QmlPrint.pp#ty ty;
+ false, expr
+
+let top_lambda gamma args ret cps_info =
+ let finalize statements =
+ match flush_tmp_var () with
+ | [] -> statements
+ | vars -> List.rev_map_append CS.def vars statements
+ in
+ match cps_info with
+ | `unskipped cps ->
+ (* function(..., k){
+ if (k==undefined){
+ return (uncps(expr))(...);
+ } else {
+ return expr(..., cont_native(k))}
+ }
+ }
+ *)
+ let nb, _, rparams, rargs = project_lambda_args 0 gamma args (`opa2js `cps) in
+ let k = genid 0 (nb+1) in
+ let params = List.rev (k::rparams) in
+ let cpse =
+ let args = List.rev ((cont_native 0 gamma k ret)::rargs) in
+ CS.return (CE.call (CE.exprident cps) args)
+ in
+ let uncps =
+ let uncps = CE.call (CE.native_global "uncps") [CE.null (); CE.exprident cps] in
+ let _, ret = project 0 gamma (CE.call uncps (List.rev rargs)) ret (`opa2js `cps) in
+ CS.return ret
+ in
+ CE.function_ None params
+ (finalize [
+ CS.if_ (CE.equality (CE.ident k) (CE.undefined ()))
+ uncps cpse
+ ])
+
+ | `skipped (skip, cps) ->
+ (* function(..., k){
+ if(k == undefined) return skip(...)
+ else return cps(..., cont_native(k))
+ }
+ *)
+ let nb, _, rparams, rargs = project_lambda_args 0 gamma args (`opa2js `no) in
+ let _, _, _, cpsrargs = project_lambda_args 0 gamma args (`opa2js `cps) in
+ let k = genid 0 (nb+1) in
+ let params = List.rev (k::rparams) in
+ let cps =
+ let args = List.rev ((cont_native 0 gamma k ret)::cpsrargs) in
+ CS.return (CE.call (CE.exprident cps) args)
+ in
+ let skip =
+ CS.return (CE.call (CE.exprident skip) (List.rev rargs))
+ in
+ CE.function_ None params
+ (finalize [
+ CS.if_ (CE.equality (CE.ident k) (CE.undefined ()))
+ skip cps
+ ])
+
+let rec info_to_js gamma info =
+ Format.eprintf "Information: %a\n%!" pp_info info;
+ match QmlTypesUtils.Inspect.follow_alias_noopt gamma info.type_ with
+ | Q.TypeArrow (args, ret) ->
+ assert (info.fields = StringMap.empty);
+ top_lambda gamma args ret info.cps
+ | _ ->
+ match info.cps with
+ | `unskipped ident ->
+ if StringMap.is_empty info.fields then
+ snd (project 0 gamma (CE.exprident ident) info.type_ (`opa2js `cps))
+ else
+ CE.obj (
+ StringMap.fold
+ (fun s info acc -> (s, info_to_js gamma info) :: acc)
+ info.fields []
+ )
+ | _ -> assert false
+
+let process {gamma; infos; package} =
+ (CS.expr
+ (CE.call
+ (CE.native_global "require")
+ [CE.string (Printf.sprintf "%s.opx" (fst package))])
+ ) :: (
+ StringMap.fold
+ (fun s info acc ->
+ let expr = info_to_js gamma info in
+ let exports = CE.native_global "exports" in
+ let exports = CE.dot exports s in
+ (CS.expr (CE.assign exports expr)) :: acc
+ ) infos []
+ )