Skip to content

Commit

Permalink
Support ES import and remove JavaScript.require
Browse files Browse the repository at this point in the history
  • Loading branch information
minoki committed Jul 29, 2023
1 parent 4a5f9c7 commit f3cde37
Show file tree
Hide file tree
Showing 28 changed files with 396 additions and 131 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ src/syntax.grm.sig
src/syntax.grm.sml
test/*/*.lua
test/*/*/*.lua
test/*/*.js
test/*/*/*.js
test/*/*.mjs
test/*/*/*.mjs
test/**/*.output
bin/lunarml
thirdparty/smlnj-lib/
Expand Down
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ bin/lunarml.gen2.lua: src/lunarml-lunarml.mlb bin/lunarml $(sources)
bin/lunarml.gen2-luajit.lua: src/lunarml-lunarml.mlb bin/lunarml $(sources)
bin/lunarml compile --luajit -o $@ $<

bin/lunarml.gen2.js: src/lunarml-lunarml.mlb bin/lunarml $(sources)
bin/lunarml.gen2.mjs: src/lunarml-lunarml.mlb bin/lunarml $(sources)
bin/lunarml compile --js-cps -o $@ $<

src/syntax.grm.sml src/syntax.grm.sig: src/syntax.grm
Expand Down Expand Up @@ -106,17 +106,17 @@ validate-luajit: bin/lunarml
diff --report-identical-files lunarml.gen2-luajit.lua lunarml.gen3-luajit.lua

validate-js: bin/lunarml
bin/lunarml compile -o lunarml.gen2.js --js-cps --print-timings src/lunarml-lunarml.mlb
$(NODE) lunarml.gen2.js -Blib/lunarml compile -o lunarml.gen3.js --js-cps --print-timings src/lunarml-lunarml.mlb
diff --report-identical-files lunarml.gen2.js lunarml.gen3.js
bin/lunarml compile -o lunarml.gen2.mjs --js-cps --print-timings src/lunarml-lunarml.mlb
$(NODE) lunarml.gen2.mjs -Blib/lunarml compile -o lunarml.gen3.mjs --js-cps --print-timings src/lunarml-lunarml.mlb
diff --report-identical-files lunarml.gen2.mjs lunarml.gen3.mjs

verify-lua: bin/lunarml bin/lunarml.gen2.lua
$(MAKE) -C test verify-lua VARIANT=lua LUA=$(LUA) LUNARML_GEN2="$(LUA) ../bin/lunarml.gen2.lua" VARIANT_GEN2=gen2

verify-luajit: bin/lunarml bin/lunarml.gen2-luajit.lua
$(MAKE) -C test verify-luajit VARIANT=luajit LUAJIT=$(LUAJIT) LUNARML_GEN2="$(LUAJIT) ../bin/lunarml.gen2-luajit.lua" VARIANT_GEN2=gen2-luajit

verify-js: bin/lunarml bin/lunarml.gen2.js
$(MAKE) -C test verify-nodejs-cps VARIANT=nodejs-cps NODE=$(NODE) LUNARML_GEN2="$(NODE) ../bin/lunarml.gen2.js" VARIANT_GEN2=gen2
verify-js: bin/lunarml bin/lunarml.gen2.mjs
$(MAKE) -C test verify-nodejs-cps VARIANT=nodejs-cps NODE=$(NODE) LUNARML_GEN2="$(NODE) ../bin/lunarml.gen2.mjs" VARIANT_GEN2=gen2

.PHONY: all typecheck test test-lua test-lua-continuations test-luajit test-nodejs test-nodejs-cps validate-lua validate-luajit validate-js verify-lua verify-luajit verify-js
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ Targets:
* `--lua-continuations`: Targets Lua 5.3+. Supports one-shot delimited continuations. Also, supports deeply nested `handle`.
* `--luajit`: Targets LuaJIT.
* JavaScript (ES2020+)
* `--js`: Produces a JavaScript program.
* `--js-cps`: Produces a JavaScript program (CPS mode; supports delimited continuations).
* `--js`: Produces a JavaScript program. The default extension is `.mjs`.
* `--js-cps`: Produces a JavaScript program (CPS mode; supports delimited continuations). The default extension is `.mjs`.

Output type:

Expand Down
32 changes: 31 additions & 1 deletion doc/JavaScriptInterface.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,40 @@ structure JavaScript : sig
val decodeUtf8 : string -> WideString.string
val toInt32 : value -> int
val toUint32 : value -> word
val require : value (* Node.js *)
end
```

## ES module

You can use `_esImport` declaration to import ECMAScript modules.

```sml
_esImport "module-name"; (* side-effect only *)
_esImport <attrs> <vid> from "module-name"; (* default import *)
_esImport <attrs> { <spec>, <spec>... } from "module-name"; (* named imports *)
_esImport <attrs> <vid>, { <spec>, <spec>... } from "module-name"; (* default and named imports *)
(*
<attrs> ::= (* default; the module may have side-effects *)
| [pure] (* allow dead-code elimination *)
<spec> ::= <vid>
| <vid> as <vid>
| <string> as <vid>
| <vid> : <ty>
| <vid> as <vid> : <ty>
| <string> as <vid> : <ty>
*)
```

Examples:

```sml
_esImport "module-name"; (* -> import "module-name"; *)
_esImport defaultItem from "module-name"; (* -> import defaultItem from "module-name"; *)
_esImport [pure] defaultExport from "module-name"; (* -> import defaultExport from "module-name"; with dead-code elimination enabled *)
_esImport [pure] { foo, bar as barr, "fun" as fun' } from "module-name"; (* -> import { foo, bar as barr, fun as fun$PRIME } from "module-name"; with dead-code elimination enabled *)
_esImport defaultItem, { foo, bar as barr, "fun" as fun' } from "module-name"; (* -> import defaultItem, { foo, bar as barr, fun as fun$PRIME } from "module-name"; *)
```

## Internal representation

Primitives
Expand Down
2 changes: 0 additions & 2 deletions lib/lunarml/ml/basis/js-common/javascript.sml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ structure JavaScript : sig
val decodeUtf8 : string -> WideString.string
val toInt32 : value -> int
val toUint32 : value -> word
val require : value (* Node.js *)
structure Lib : sig
val parseFloat : value
val Number : value
Expand Down Expand Up @@ -129,7 +128,6 @@ fun method (obj, name) args = _primCall "JavaScript.method" (obj, name, args)
val function = _Prim.JavaScript.function
val encodeUtf8 = _Prim.JavaScript.encodeUtf8
val decodeUtf8 = _Prim.JavaScript.decodeUtf8
val require = _Prim.JavaScript.require
fun unsafeToValue x : value = _primCall "Unsafe.cast" (x)
fun unsafeFromValue (x : value) = _primCall "Unsafe.cast" (x)
val fromBool : bool -> value = unsafeToValue
Expand Down
3 changes: 1 addition & 2 deletions lib/lunarml/ml/basis/nodejs/command-line.sml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ structure CommandLine : sig
val name : unit -> string
val arguments : unit -> string list
end = struct
local val process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "process"]) ()
val argv = LunarML.assumeDiscardable JavaScript.field (process, "argv")
local _esImport [pure] { argv } from "node:process";
in
fun name () = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.sub (argv, JavaScript.fromInt 1)))
fun arguments () = let val n = JavaScript.unsafeFromValue (JavaScript.field (argv, "length"))
Expand Down
44 changes: 25 additions & 19 deletions lib/lunarml/ml/basis/nodejs/os-async.sml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,16 @@ structure OS :> sig
end = struct
type syserror = string
exception SysErr of string * syserror option
local val fs = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "fs"]) ()
val process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "process"]) ()
val child_process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "child_process"]) ()
local
structure fs = struct
_esImport [pure] { mkdirSync, rmdirSync, statSync, lstatSync, readlinkSync, rmSync, renameSync } from "node:fs";
end
structure process = struct
_esImport [pure] { chdir, cwd, exit, env } from "node:process";
end
structure child_process = struct
_esImport [pure] { execSync } from "node:child_process";
end
in
structure FileSys = struct
(*
Expand All @@ -70,32 +77,32 @@ val fileId : string -> file_id
val hash : file_id -> word
val compare : file_id * file_id -> order
*)
fun chDir (dir : string) = ( JavaScript.method (process, "chdir") #[JavaScript.fromWideString (JavaScript.decodeUtf8 dir)]
fun chDir (dir : string) = ( JavaScript.call process.chdir #[JavaScript.fromWideString (JavaScript.decodeUtf8 dir)]
; () (* TODO: raise SysErr *)
)
fun getDir () : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.method (process, "cwd") #[]))
fun getDir () : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.call process.cwd #[]))
fun mkDir (path : string) = let val options = JavaScript.newObject ()
in JavaScript.set (options, JavaScript.fromWideString "recursive", JavaScript.fromBool true)
; JavaScript.method (fs, "mkdirSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path), options]
; JavaScript.call fs.mkdirSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path), options]
; ()
end
fun rmDir (path : string) = ( JavaScript.method (fs, "rmdirSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun rmDir (path : string) = ( JavaScript.call fs.rmdirSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
; ()
)
fun isDir (path : string) = let val stat = JavaScript.method (fs, "statSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun isDir (path : string) = let val stat = JavaScript.call fs.statSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
in JavaScript.unsafeFromValue (JavaScript.method (stat, "isDirectory") #[])
end
fun isLink (path : string) = let val stat = JavaScript.method (fs, "lstatSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun isLink (path : string) = let val stat = JavaScript.call fs.lstatSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
in JavaScript.unsafeFromValue (JavaScript.method (stat, "isSymbolicLink") #[])
end
fun readLink (path : string) : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.method (fs, "readlinkSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]))
fun readLink (path : string) : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.call fs.readlinkSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]))
(* fullPath, realPath, modTime, fileSize, setTime *)
fun remove (path : string) = ( JavaScript.method (fs, "rmSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun remove (path : string) = ( JavaScript.call fs.rmSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
; ()
)
fun rename {old : string, new : string} = ( JavaScript.method (fs, "renameSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 old), JavaScript.fromWideString (JavaScript.decodeUtf8 new)]
; ()
)
fun rename { old : string, new : string } = ( JavaScript.call fs.renameSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 old), JavaScript.fromWideString (JavaScript.decodeUtf8 new)]
; ()
)
end (* structure FileSys *)
structure IO = struct end
structure Path = struct
Expand Down Expand Up @@ -220,18 +227,17 @@ type status = int
val success : status = 0
val failure : status = 1
val isSuccess : status -> bool = fn 0 => true | _ => false
fun system (command : string) = ( JavaScript.method (child_process, "execSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 command)]
fun system (command : string) = ( JavaScript.call child_process.execSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 command)]
; success (* TODO: catch failures *)
)
(* val atExit : (unit -> unit) -> unit *)
fun exit (status : status) : 'a = ( JavaScript.method (process, "exit") #[JavaScript.fromInt status]
fun exit (status : status) : 'a = ( JavaScript.call process.exit #[JavaScript.fromInt status]
; _primCall "unreachable" ()
)
fun terminate (status : status) : 'a = ( JavaScript.method (process, "exit") #[JavaScript.fromInt status]
fun terminate (status : status) : 'a = ( JavaScript.call process.exit #[JavaScript.fromInt status]
; _primCall "unreachable" ()
)
fun getEnv (name : string) : string option = let val env = JavaScript.field (process, "env")
val value = JavaScript.field (env, JavaScript.decodeUtf8 name)
fun getEnv (name : string) : string option = let val value = JavaScript.field (process.env, JavaScript.decodeUtf8 name)
in if JavaScript.typeof value = "string" then
SOME (JavaScript.encodeUtf8 (JavaScript.unsafeFromValue value))
else
Expand Down
44 changes: 25 additions & 19 deletions lib/lunarml/ml/basis/nodejs/os.sml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ structure OS :> sig
end = struct
type syserror = string
exception SysErr of string * syserror option
local val fs = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "fs"]) ()
val process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "process"]) ()
val child_process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "child_process"]) ()
local
structure fs = struct
_esImport [pure] { mkdirSync, rmdirSync, statSync, lstatSync, readlinkSync, rmSync, renameSync } from "node:fs";
end
structure process = struct
_esImport [pure] { chdir, cwd, exit, env } from "node:process";
end
structure child_process = struct
_esImport [pure] { execSync } from "node:child_process";
end
in
structure FileSys = struct
(*
Expand All @@ -66,32 +73,32 @@ val fileId : string -> file_id
val hash : file_id -> word
val compare : file_id * file_id -> order
*)
fun chDir (dir : string) = ( JavaScript.method (process, "chdir") #[JavaScript.fromWideString (JavaScript.decodeUtf8 dir)]
fun chDir (dir : string) = ( JavaScript.call process.chdir #[JavaScript.fromWideString (JavaScript.decodeUtf8 dir)]
; () (* TODO: raise SysErr *)
)
fun getDir () : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.method (process, "cwd") #[]))
fun getDir () : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.call process.cwd #[]))
fun mkDir (path : string) = let val options = JavaScript.newObject ()
in JavaScript.set (options, JavaScript.fromWideString "recursive", JavaScript.fromBool true)
; JavaScript.method (fs, "mkdirSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path), options]
; JavaScript.call fs.mkdirSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path), options]
; ()
end
fun rmDir (path : string) = ( JavaScript.method (fs, "rmdirSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun rmDir (path : string) = ( JavaScript.call fs.rmdirSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
; ()
)
fun isDir (path : string) = let val stat = JavaScript.method (fs, "statSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun isDir (path : string) = let val stat = JavaScript.call fs.statSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
in JavaScript.unsafeFromValue (JavaScript.method (stat, "isDirectory") #[])
end
fun isLink (path : string) = let val stat = JavaScript.method (fs, "lstatSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun isLink (path : string) = let val stat = JavaScript.call fs.lstatSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
in JavaScript.unsafeFromValue (JavaScript.method (stat, "isSymbolicLink") #[])
end
fun readLink (path : string) : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.method (fs, "readlinkSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]))
fun readLink (path : string) : string = JavaScript.encodeUtf8 (JavaScript.unsafeFromValue (JavaScript.call fs.readlinkSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]))
(* fullPath, realPath, modTime, fileSize, setTime *)
fun remove (path : string) = ( JavaScript.method (fs, "rmSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
fun remove (path : string) = ( JavaScript.call fs.rmSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 path)]
; ()
)
fun rename {old : string, new : string} = ( JavaScript.method (fs, "renameSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 old), JavaScript.fromWideString (JavaScript.decodeUtf8 new)]
; ()
)
fun rename { old : string, new : string } = ( JavaScript.call fs.renameSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 old), JavaScript.fromWideString (JavaScript.decodeUtf8 new)]
; ()
)
end (* structure FileSys *)
structure IO = struct end
structure Path = struct
Expand Down Expand Up @@ -193,18 +200,17 @@ type status = int
val success : status = 0
val failure : status = 1
val isSuccess : status -> bool = fn 0 => true | _ => false
fun system (command : string) = ( JavaScript.method (child_process, "execSync") #[JavaScript.fromWideString (JavaScript.decodeUtf8 command)]
fun system (command : string) = ( JavaScript.call child_process.execSync #[JavaScript.fromWideString (JavaScript.decodeUtf8 command)]
; success (* TODO: catch failures *)
)
(* val atExit : (unit -> unit) -> unit *)
fun exit (status : status) : 'a = ( JavaScript.method (process, "exit") #[JavaScript.fromInt status]
fun exit (status : status) : 'a = ( JavaScript.call process.exit #[JavaScript.fromInt status]
; _primCall "unreachable" ()
)
fun terminate (status : status) : 'a = ( JavaScript.method (process, "exit") #[JavaScript.fromInt status]
fun terminate (status : status) : 'a = ( JavaScript.call process.exit #[JavaScript.fromInt status]
; _primCall "unreachable" ()
)
fun getEnv (name : string) : string option = let val env = JavaScript.field (process, "env")
val value = JavaScript.field (env, JavaScript.decodeUtf8 name)
fun getEnv (name : string) : string option = let val value = JavaScript.field (process.env, JavaScript.decodeUtf8 name)
in if JavaScript.typeof value = "string" then
SOME (JavaScript.encodeUtf8 (JavaScript.unsafeFromValue value))
else
Expand Down
9 changes: 2 additions & 7 deletions lib/lunarml/ml/basis/nodejs/text-io-async.sml
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,8 @@ structure TextIO :> sig
val print : string -> unit
end = struct
local
val process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "process"]) ()
val fs = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "fs"]) ()
val createReadStream = LunarML.assumeDiscardable JavaScript.field (fs, "createReadStream")
val createWriteStream = LunarML.assumeDiscardable JavaScript.field (fs, "createWriteStream")
val stdin = LunarML.assumeDiscardable JavaScript.field (process, "stdin")
val stdout = LunarML.assumeDiscardable JavaScript.field (process, "stdout")
val stderr = LunarML.assumeDiscardable JavaScript.field (process, "stderr")
_esImport [pure] { stdin, stdout, stderr } from "node:process";
_esImport [pure] { createReadStream, createWriteStream } from "node:fs";
structure Instream :> sig
type instream
type vector = string
Expand Down
3 changes: 1 addition & 2 deletions lib/lunarml/ml/basis/nodejs/text-io.sml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ structure TextIO :> sig
val print : string -> unit
end = struct
local
val process = LunarML.assumeDiscardable (fn () => JavaScript.call JavaScript.require #[JavaScript.fromWideString "process"]) ()
val stdout = LunarML.assumeDiscardable JavaScript.field (process, "stdout")
_esImport [pure] { stdout } from "node:process";
in
fun print (s : string) = ( JavaScript.method (stdout, "write") #[JavaScript.unsafeToValue s]; ())
end
Expand Down
Loading

0 comments on commit f3cde37

Please sign in to comment.