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 [] + )