diff --git a/lib/elixir_script/lib/js.ex b/lib/elixir_script/lib/js.ex index a8713880..367c8705 100644 --- a/lib/elixir_script/lib/js.ex +++ b/lib/elixir_script/lib/js.ex @@ -91,4 +91,9 @@ defmodule ElixirScript.JS do ``` """ defexternal object_to_map(object, options) + + @doc """ + Turns an Elixir function into a JavaScript one + """ + defexternal to_js_function(function) end diff --git a/lib/elixir_script/module_systems/namespace.ex b/lib/elixir_script/module_systems/namespace.ex index c98eee87..663110af 100644 --- a/lib/elixir_script/module_systems/namespace.ex +++ b/lib/elixir_script/module_systems/namespace.ex @@ -47,7 +47,7 @@ defmodule ElixirScript.ModuleSystems.Namespace do exports = [JS.return_statement(JS.identifier("__exports"))] make = JS.member_expression( - Helpers.call( + Helpers.call_sync( build_namespace(), [JS.identifier("Elixir"), JS.literal(Enum.join(["Elixir"] ++ Module.split(module_name), "."))] ), diff --git a/lib/elixir_script/passes/output/js_module.ex b/lib/elixir_script/passes/output/js_module.ex index 9277d826..df35e0ee 100644 --- a/lib/elixir_script/passes/output/js_module.ex +++ b/lib/elixir_script/passes/output/js_module.ex @@ -14,7 +14,27 @@ defmodule ElixirScript.Output.JSModule do end def start do - normal = Helpers.symbol("normal") + start_process_call = Helpers.call_sync( + J.member_expression( + Helpers.process_system(), + J.identifier("spawn") + ), + [ + Helpers.call_sync( + J.member_expression( + J.identifier(:app), + J.identifier("__load") + ), + [J.identifier("Elixir")] + ), + J.literal("start"), + J.array_expression([ + Helpers.symbol("normal"), + J.identifier(:args) + ]) + ] + ) + Helpers.assign( J.member_expression( @@ -24,19 +44,7 @@ defmodule ElixirScript.Output.JSModule do Helpers.function( [J.identifier(:app), J.identifier(:args)], J.block_statement([ - Helpers.call( - J.member_expression( - Helpers.call( - J.member_expression( - J.identifier(:app), - J.identifier("__load") - ), - [J.identifier("Elixir")] - ), - J.identifier("start") - ), - [normal, J.identifier(:args)] - ) + start_process_call ]) ) ) @@ -52,7 +60,7 @@ defmodule ElixirScript.Output.JSModule do [J.identifier(:module)], J.block_statement([ J.return_statement( - Helpers.call( + Helpers.call_sync( J.member_expression( J.identifier(:module), J.identifier("__load") diff --git a/lib/elixir_script/passes/translate/clause.ex b/lib/elixir_script/passes/translate/clause.ex index 38c42898..b8d47268 100644 --- a/lib/elixir_script/passes/translate/clause.ex +++ b/lib/elixir_script/passes/translate/clause.ex @@ -17,9 +17,9 @@ defmodule ElixirScript.Translate.Clause do body = body |> return_last_statement - |> Function.update_last_call(state) + #|> Function.update_last_call(state) - ast = Helpers.call( + ast = Helpers.call_sync( J.member_expression( Helpers.patterns(), J.identifier("clause") diff --git a/lib/elixir_script/passes/translate/form.ex b/lib/elixir_script/passes/translate/form.ex index 3f580783..5d2dc2f7 100644 --- a/lib/elixir_script/passes/translate/form.ex +++ b/lib/elixir_script/passes/translate/form.ex @@ -4,9 +4,8 @@ defmodule ElixirScript.Translate.Form do # Handles translation of all forms that are not functions or clauses alias ESTree.Tools.Builder, as: J - alias ElixirScript.Translate.Helpers + alias ElixirScript.Translate.{Helpers, Clause} alias ElixirScript.Translate.Forms.{Bitstring, Match, Try, For, Receive, Remote, Pattern, With} - alias ElixirScript.Translate.Clause require Logger @spec compile!(any, map) :: ESTree.Node.t @@ -37,7 +36,7 @@ defmodule ElixirScript.Translate.Form do end def compile({:|, _, [head, tail]}, state) do - ast = Helpers.call( + ast = Helpers.call_sync( J.member_expression( Helpers.functions(), J.identifier("concat") @@ -145,17 +144,17 @@ defmodule ElixirScript.Translate.Form do end def compile({:case, _, [condition, [do: clauses]]}, state) do - func = Helpers.call( + ast = Helpers.call_gen( J.member_expression( - Helpers.patterns(), - J.identifier("defmatch") + Helpers.special_forms(), + J.identifier("_case") ), - Enum.map(clauses, fn x -> Clause.compile(x, state) |> elem(0) end) |> List.flatten - ) - - ast = Helpers.call( - J.member_expression( func, J.identifier("call")), - [J.identifier(:this), compile!(condition, state)] + [ + compile!(condition, state), + Enum.map(clauses, fn x -> Clause.compile(x, state) |> elem(0) end) + |> List.flatten + |> J.array_expression() + ] ) { ast, state } @@ -182,7 +181,7 @@ defmodule ElixirScript.Translate.Form do J.identifier("cond") ) - ast = Helpers.call( + ast = Helpers.call_gen( cond_function, processed_clauses ) diff --git a/lib/elixir_script/passes/translate/forms/bitstring.ex b/lib/elixir_script/passes/translate/forms/bitstring.ex index e4d2cc2a..2cebccdf 100644 --- a/lib/elixir_script/passes/translate/forms/bitstring.ex +++ b/lib/elixir_script/passes/translate/forms/bitstring.ex @@ -81,7 +81,7 @@ defmodule ElixirScript.Translate.Forms.Bitstring do end defp do_compile_element({type, ast}) do - Helpers.call( + Helpers.call_sync( JS.member_expression( Helpers.bitstring(), JS.identifier(type) @@ -93,7 +93,7 @@ defmodule ElixirScript.Translate.Forms.Bitstring do end defp do_compile_element({type, ast, params}) when is_list(params) do - Helpers.call( + Helpers.call_sync( JS.member_expression( Helpers.bitstring(), JS.identifier(type) diff --git a/lib/elixir_script/passes/translate/forms/for.ex b/lib/elixir_script/passes/translate/forms/for.ex index ad3e9c0f..c8ddb92e 100644 --- a/lib/elixir_script/passes/translate/forms/for.ex +++ b/lib/elixir_script/passes/translate/forms/for.ex @@ -15,7 +15,7 @@ defmodule ElixirScript.Translate.Forms.For do fun = args.fun - expression = Helpers.call( + expression = Helpers.call_sync( JS.member_expression( Helpers.patterns(), JS.identifier("clause") @@ -25,12 +25,12 @@ defmodule ElixirScript.Translate.Forms.For do members = ["Elixir", "Collectable" , "__load"] - collectable = Helpers.call( + collectable = Helpers.call_sync( Identifier.make_namespace_members(members), [JS.identifier("Elixir")] ) - ast = Helpers.call( + ast = Helpers.call_gen( JS.member_expression( Helpers.special_forms(), JS.identifier("_for") diff --git a/lib/elixir_script/passes/translate/forms/js.ex b/lib/elixir_script/passes/translate/forms/js.ex index 03fb0f0a..67a93263 100644 --- a/lib/elixir_script/passes/translate/forms/js.ex +++ b/lib/elixir_script/passes/translate/forms/js.ex @@ -148,4 +148,18 @@ defmodule ElixirScript.Translate.Forms.JS do {ast, state} end + + def compile({{:., _, [ElixirScript.JS, :to_js_function]}, _, [func]}, state) do + ast = Helpers.call_sync( + J.member_expression( + Helpers.functions(), + J.identifier("to_js_function") + ), + [ + Form.compile!(func, state) + ] + ) + + {ast, state} + end end diff --git a/lib/elixir_script/passes/translate/forms/match.ex b/lib/elixir_script/passes/translate/forms/match.ex index db146339..b14c2f8f 100644 --- a/lib/elixir_script/passes/translate/forms/match.ex +++ b/lib/elixir_script/passes/translate/forms/match.ex @@ -65,10 +65,10 @@ defmodule ElixirScript.Translate.Forms.Match do { patterns, params, state } = Pattern.compile([left], state) - array_pattern = Helpers.declare(params, Helpers.call( + array_pattern = Helpers.declare(params, Helpers.call_gen( J.member_expression( Helpers.patterns(), - J.identifier("match") + J.identifier("match_gen") ), [hd(patterns), right_ast] )) @@ -112,10 +112,10 @@ defmodule ElixirScript.Translate.Forms.Match do {js_ast, state} = Enum.map_reduce(lefts, state, fn(left, state) -> { patterns, params, state } = Pattern.compile([left], state) - array_pattern = Helpers.declare(params, Helpers.call( + array_pattern = Helpers.declare(params, Helpers.call_gen( J.member_expression( Helpers.patterns(), - J.identifier("match") + J.identifier("match_gen") ), [hd(patterns), J.identifier("__intermediate__")] )) diff --git a/lib/elixir_script/passes/translate/forms/pattern/patterns.ex b/lib/elixir_script/passes/translate/forms/pattern/patterns.ex index eb873149..206297b4 100644 --- a/lib/elixir_script/passes/translate/forms/pattern/patterns.ex +++ b/lib/elixir_script/passes/translate/forms/pattern/patterns.ex @@ -40,56 +40,56 @@ defmodule ElixirScript.Translate.Forms.Pattern.Patterns do ) def parameter() do - Helpers.call( + Helpers.call_sync( @parameter, [] ) end def parameter(name) do - Helpers.call( + Helpers.call_sync( @parameter, [name] ) end def head_tail(headParameter, tailParameter) do - Helpers.call( + Helpers.call_sync( @head_tail, [headParameter, tailParameter] ) end def starts_with(prefix) do - Helpers.call( + Helpers.call_sync( @starts_with, [J.literal(prefix)] ) end def capture(value) do - Helpers.call( + Helpers.call_sync( @capture, [value] ) end def bound(value) do - Helpers.call( + Helpers.call_sync( @bound, [value] ) end def type(prototype, value) do - Helpers.call( + Helpers.call_sync( @_type, [prototype, value] ) end def bitstring_match(values) do - Helpers.call( + Helpers.call_sync( @bitstring_match, values ) diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index 918f986d..3c39c2c5 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -85,7 +85,7 @@ defmodule ElixirScript.Translate.Forms.Remote do module === Elixir -> members = ["Elixir", "__load"] - Helpers.call( + Helpers.call_sync( Identifier.make_namespace_members(members), [J.identifier("Elixir")] ) @@ -99,7 +99,7 @@ defmodule ElixirScript.Translate.Forms.Remote do ElixirScript.Translate.Module.is_elixir_module(module) -> members = ["Elixir"] ++ Module.split(module) ++ ["__load"] - Helpers.call( + Helpers.call_sync( Identifier.make_namespace_members(members), [J.identifier("Elixir")] ) diff --git a/lib/elixir_script/passes/translate/forms/try.ex b/lib/elixir_script/passes/translate/forms/try.ex index 249d8f9b..dc3d1a69 100644 --- a/lib/elixir_script/passes/translate/forms/try.ex +++ b/lib/elixir_script/passes/translate/forms/try.ex @@ -39,7 +39,7 @@ defmodule ElixirScript.Translate.Forms.Try do JS.identifier(:null) end - js_ast = Helpers.call( + js_ast = Helpers.call_gen( JS.member_expression( Helpers.special_forms(), JS.identifier("_try") @@ -67,10 +67,10 @@ defmodule ElixirScript.Translate.Forms.Try do end) - Helpers.call( + Helpers.call_gen( JS.member_expression( Helpers.patterns(), - JS.identifier("defmatch") + JS.identifier("defmatchGen") ), processed_clauses ) diff --git a/lib/elixir_script/passes/translate/forms/with.ex b/lib/elixir_script/passes/translate/forms/with.ex index 2bc5ea08..9db4a681 100644 --- a/lib/elixir_script/passes/translate/forms/with.ex +++ b/lib/elixir_script/passes/translate/forms/with.ex @@ -33,7 +33,7 @@ defmodule ElixirScript.Translate.Forms.With do expressions = result.expressions - js_ast = Helpers.call( + js_ast = Helpers.call_gen( JS.member_expression( Helpers.special_forms(), JS.identifier("_with") diff --git a/lib/elixir_script/passes/translate/function.ex b/lib/elixir_script/passes/translate/function.ex index 23888453..6701a136 100644 --- a/lib/elixir_script/passes/translate/function.ex +++ b/lib/elixir_script/passes/translate/function.ex @@ -19,8 +19,7 @@ defmodule ElixirScript.Translate.Function do arg_matches_declaration = Helpers.declare_let("__arg_matches__", J.identifier("null")) - function_recur_dec = Helpers.function( - "recur", + function_dec = Helpers.arrow_function( [J.rest_element(J.identifier("__function_args__"))], J.block_statement([ arg_matches_declaration, @@ -37,15 +36,15 @@ defmodule ElixirScript.Translate.Function do ]) ) - function_dec = Helpers.arrow_function( - [J.rest_element(J.identifier("__function_args__"))], - J.block_statement([ - function_recur_dec, - J.return_statement( - trampoline() - ) - ]) - ) + #function_dec = Helpers.arrow_function( + # [J.rest_element(J.identifier("__function_args__"))], + # J.block_statement([ + # function_recur_dec, + # J.return_statement( + # trampoline() + # ) + # ]) + #) state = Map.put(state, :anonymous_fn, anonymous?) { function_dec, state } @@ -62,8 +61,8 @@ defmodule ElixirScript.Translate.Function do arg_matches_declaration = Helpers.declare_let("__arg_matches__", J.identifier("null")) intermediate_declaration = Helpers.declare_let("__intermediate__", J.identifier("null")) - function_recur_dec = Helpers.function( - "recur", + function_dec = Helpers.function( + ElixirScript.Translate.Identifier.make_function_name(name), [J.rest_element(J.identifier("__function_args__"))], J.block_statement([ arg_matches_declaration, @@ -81,16 +80,16 @@ defmodule ElixirScript.Translate.Function do ]) ) - function_dec = Helpers.function( - ElixirScript.Translate.Identifier.make_function_name(name), - [J.rest_element(J.identifier("__function_args__"))], - J.block_statement([ - function_recur_dec, - J.return_statement( - trampoline() - ) - ]) - ) + #function_dec = Helpers.function( + # ElixirScript.Translate.Identifier.make_function_name(name), + # [J.rest_element(J.identifier("__function_args__"))], + # J.block_statement([ + # function_recur_dec, + # J.return_statement( + # trampoline() + # ) + # ]) + #) { function_dec, state } end @@ -99,10 +98,10 @@ defmodule ElixirScript.Translate.Function do clauses |> Enum.map(&compile_clause(&1, state)) |> Enum.map(fn {patterns, _params, guards, body} -> - match_or_default_call = Helpers.call( + match_or_default_call = Helpers.call_gen( J.member_expression( Helpers.patterns(), - J.identifier("match_or_default") + J.identifier("match_or_default_gen") ), [J.array_expression(patterns), J.identifier("__function_args__"), guards] ) @@ -139,9 +138,9 @@ defmodule ElixirScript.Translate.Function do body = body |> Clause.return_last_statement - |> update_last_call(state) + #|> update_last_call(state) - declaration = Helpers.declare_let(params, J.identifier("__arg_matches__")) + declaration = Helpers.declare(params, J.identifier("__arg_matches__")) body = [declaration] ++ body {patterns, params, guard, body} @@ -164,7 +163,9 @@ defmodule ElixirScript.Translate.Function do nil -> J.identifier("null") {:__block__, _, block_body} -> - {list, _} = Enum.map_reduce(block_body, state, &Form.compile(&1, &2)) + {list, _} = Enum.map_reduce(block_body, state, fn(x, acc) -> + Form.compile(x, acc) + end) List.flatten(list) _ -> Form.compile!(block, state) @@ -197,7 +198,7 @@ defmodule ElixirScript.Translate.Function do end defp recur_bind(args) do - Helpers.call( + Helpers.call_sync( J.member_expression( J.identifier("recur"), J.identifier("bind") @@ -219,7 +220,7 @@ defmodule ElixirScript.Translate.Function do end defp trampoline() do - Helpers.call( + Helpers.call_sync( J.member_expression( Helpers.functions(), J.identifier("trampoline") diff --git a/lib/elixir_script/passes/translate/helpers.ex b/lib/elixir_script/passes/translate/helpers.ex index 227eab39..fe76900a 100644 --- a/lib/elixir_script/passes/translate/helpers.ex +++ b/lib/elixir_script/passes/translate/helpers.ex @@ -19,18 +19,40 @@ defmodule ElixirScript.Translate.Helpers do ) end + def call_sync(callee, arguments) do + J.call_expression( + callee, + arguments + ) + end + def call(callee, arguments) do + J.call_expression( + J.member_expression(core_module("ProcessSystem"), J.identifier("run")), + [ + callee, + J.array_expression(arguments) + ] + ) + |> J.yield_expression(true) + end + + def call_gen(callee, arguments) do J.call_expression( callee, arguments ) + |> J.yield_expression(true) end def arrow_function(params, body) do - J.arrow_function_expression( + J.function_expression( params, [], - body + body, + true, + false, + false ) end @@ -39,7 +61,10 @@ defmodule ElixirScript.Translate.Helpers do name, params, [], - body + body, + true, + false, + false ) end @@ -89,6 +114,13 @@ defmodule ElixirScript.Translate.Helpers do core_module("SpecialForms") end + def process_system do + J.member_expression( + core_module("global"), + J.identifier("__elixirscript_process_system__") + ) + end + def declare(%ESTree.Identifier{} = name, value) do declarator = J.variable_declarator( name, diff --git a/lib/elixir_script/passes/translate/module.ex b/lib/elixir_script/passes/translate/module.ex index c35341ca..10f7f0c7 100644 --- a/lib/elixir_script/passes/translate/module.ex +++ b/lib/elixir_script/passes/translate/module.ex @@ -210,7 +210,7 @@ defmodule ElixirScript.Translate.Module do defp make_info_function(module, state) do info_map = make_info_map(module, state) - get_call = Helpers.call( + get_call = Helpers.call_sync( J.member_expression( J.identifier("__info__map__"), J.identifier("get") diff --git a/lib/elixir_script/passes/translate/protocol.ex b/lib/elixir_script/passes/translate/protocol.ex index cfaf4912..34bfeb11 100644 --- a/lib/elixir_script/passes/translate/protocol.ex +++ b/lib/elixir_script/passes/translate/protocol.ex @@ -18,7 +18,7 @@ defmodule ElixirScript.Translate.Protocol do declaration = Helpers.declare( "protocol", - Helpers.call( + Helpers.call_sync( J.member_expression( Helpers.functions(), J.identifier(:defprotocol) @@ -44,12 +44,12 @@ defmodule ElixirScript.Translate.Protocol do Enum.map(impls, fn({impl, impl_for}) -> members = ["Elixir"] ++ Module.split(impl) ++ ["__load"] - ast = Helpers.call( + ast = Helpers.call_sync( Identifier.make_namespace_members(members), [J.identifier("Elixir")] ) - Helpers.call( + Helpers.call_sync( J.member_expression( Helpers.functions(), J.identifier(:defimpl) diff --git a/package.json b/package.json index f12b90e8..cbbfb87f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "erlang-types": "^1.0.1", "grapheme-splitter": "^1.0.2", "rollup-plugin-commonjs": "^8.2.1", - "tailored": "^2.7.4" + "tailored": "file:/Users/bryanjos/projects/basstype/tailored" }, "devDependencies": { "@std/esm": "^0.8.3", diff --git a/rollup.config.js b/rollup.config.js index a0ad88ce..39801334 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -15,10 +15,10 @@ export default { babel({ babelrc: false, }), - minify({ - keepFnName: true, - keepClassName: true, - }), + //minify({ + // keepFnName: true, + // keepClassName: true, + //}), ], output: [{ file: 'priv/build/iife/ElixirScript.Core.js', format: 'iife' }], }; diff --git a/src/javascript/lib/core.js b/src/javascript/lib/core.js index 2b3e9489..215a549e 100644 --- a/src/javascript/lib/core.js +++ b/src/javascript/lib/core.js @@ -2,6 +2,7 @@ import Patterns from 'tailored'; import ErlangTypes from 'erlang-types'; import Functions from './core/functions'; import SpecialForms from './core/special_forms'; +import ProcessSystem from './core/processes/process_system'; import erlang from './core/erlang_compat/erlang'; import maps from './core/erlang_compat/maps'; import lists from './core/erlang_compat/lists'; @@ -32,6 +33,7 @@ function get_global() { const globalState = get_global(); +globalState.__elixirscript_process_system__ = new ProcessSystem(); globalState.__elixirscript_store__ = new Map(); globalState.__elixirscript_names__ = new Map(); @@ -57,4 +59,5 @@ export default { elixir_config, math, proplists, + ProcessSystem }; diff --git a/src/javascript/lib/core/erlang_compat/lists.js b/src/javascript/lib/core/erlang_compat/lists.js index 066f903a..a77ea9f6 100644 --- a/src/javascript/lib/core/erlang_compat/lists.js +++ b/src/javascript/lib/core/erlang_compat/lists.js @@ -5,8 +5,10 @@ function reverse(list) { return [...list].reverse(); } -function foreach(fun, list) { - list.forEach(x => fun(x)); +function* foreach(fun, list) { + for (const x of list) { + yield* fun(x); + } return Symbol.for('ok'); } @@ -33,8 +35,14 @@ function flatten(deepList, tail = []) { return val.concat(tail); } -function foldl(fun, acc0, list) { - return list.reduce((acc, value) => fun(value, acc), acc0); +function* foldl(fun, acc0, list) { + let acc = acc0; + + for (const value of list) { + acc = yield* fun(value, acc); + } + + return acc; } function foldr(fun, acc0, list) { @@ -118,18 +126,22 @@ function keytake(key, n, tupleList) { const result = keyfind(key, n, tupleList); if (result !== false) { - return new ErlangTypes.Tuple(result.get(n - 1), result, keydelete(key, n, tupleList)); + return new ErlangTypes.Tuple( + result.get(n - 1), + result, + keydelete(key, n, tupleList) + ); } return false; } -function mapfoldl(fun, acc0, list1) { +function* mapfoldl(fun, acc0, list1) { const listResult = []; let accResult = acc0; for (const item of list1) { - const tuple = fun(item, accResult); + const tuple = yield* fun(item, accResult); listResult.push(tuple.get(0)); accResult = tuple.get(1); } @@ -141,19 +153,35 @@ function concat(things) { return things.map(v => v.toString()).join(); } -function map(fun, list) { - return list.map(value => fun(value)); +function* map(fun, list) { + const reList = []; + + for (const value of list) { + const result = yield* fun(value); + reList.push(result); + } + + return reList; } -function filter(pred, list1) { - return list1.filter(x => pred(x)); +function* filter(pred, list1) { + const reList = []; + + for (const value of list1) { + const result = yield* pred(value); + if (result === true) { + reList.push(value); + } + } + + return reList; } -function filtermap(fun, list1) { +function* filtermap(fun, list1) { const list2 = []; for (const item of list1) { - const value = fun(item); + const value = yield* fun(item); if (value === true) { list2.push(item); @@ -175,9 +203,9 @@ function member(elem, list) { return false; } -function all(pred, list) { +function* all(pred, list) { for (const item of list) { - if (pred(item) === false) { + if ((yield* pred(item)) === false) { return false; } } @@ -185,9 +213,9 @@ function all(pred, list) { return true; } -function any(pred, list) { +function* any(pred, list) { for (const item of list) { - if (pred(item) === true) { + if ((yield* pred(item)) === true) { return true; } } @@ -195,7 +223,7 @@ function any(pred, list) { return false; } -function splitwith(pred, list) { +function* splitwith(pred, list) { let switchToList2 = false; const list1 = []; const list2 = []; @@ -203,7 +231,7 @@ function splitwith(pred, list) { for (const item of list) { if (switchToList2 === true) { list2.push(item); - } else if (pred(item) === true) { + } else if ((yield* pred(item)) === true) { list1.push(item); } else { switchToList2 = true; @@ -214,7 +242,7 @@ function splitwith(pred, list) { return new ErlangTypes.Tuple(list1, list2); } -function sort(...args) { +function* sort(...args) { if (args.length === 1) { const list2 = [...args[0]]; return list2.sort(); @@ -223,15 +251,17 @@ function sort(...args) { const fun = args[0]; const list2 = [...args[1]]; - return list2.sort((a, b) => { - const result = fun(a, b); + const result = list2.sort(function*(a, b) { + const sortResult = yield* fun(a, b); - if (result === true) { + if (sortResult === true) { return -1; } return 1; }); + + return Promise.all(result); } export default { @@ -257,5 +287,5 @@ export default { all, any, splitwith, - sort, + sort }; diff --git a/src/javascript/lib/core/erlang_compat/maps.js b/src/javascript/lib/core/erlang_compat/maps.js index ec8b4e79..6e66e20d 100644 --- a/src/javascript/lib/core/erlang_compat/maps.js +++ b/src/javascript/lib/core/erlang_compat/maps.js @@ -88,11 +88,11 @@ function find(key, map) { return ERROR; } -function fold(fun, init, map) { +function* fold(fun, init, map) { let acc = init; for (const [key, value] of map.entries()) { - acc = fun(key, value, acc); + acc = yield* fun(key, value, acc); } return acc; diff --git a/src/javascript/lib/core/functions.js b/src/javascript/lib/core/functions.js index 86d84a65..efb4da59 100644 --- a/src/javascript/lib/core/functions.js +++ b/src/javascript/lib/core/functions.js @@ -4,10 +4,10 @@ import Core from '../core'; import proplists from './erlang_compat/proplists'; import erlang from './erlang_compat/erlang'; -function call_property(item, property) { +function* call_property(item, property) { if (!property) { if (item instanceof Function || typeof item === 'function') { - return item(); + return yield* item(); } return item; @@ -133,7 +133,10 @@ function object_to_map(object, options = []) { key2 = Symbol.for(key); } - if (value !== null && (value.constructor === Object || (value instanceof Array && opt_recurse_array))) { + if ( + value !== null && + (value.constructor === Object || (value instanceof Array && opt_recurse_array)) + ) { value = object_to_map(value, options); } map.set(key2, value); @@ -156,11 +159,11 @@ class Recurse { } } -function trampoline(f) { +function* trampoline(f) { let currentValue = f; while (currentValue && currentValue instanceof Recurse) { - currentValue = currentValue.func(); + currentValue = yield* currentValue.func(); } return currentValue; @@ -205,6 +208,17 @@ function concat(head, tail) { return [head].concat(tail); } +function to_js_function(generator) { + const genObj = generator(); + let value = null; + + while (genObj.done === false) { + value = genObj.next(value); + } + + return value; +} + export default { call_property, defprotocol, @@ -217,4 +231,5 @@ export default { split_at, graphemes, concat, + to_js_function, }; diff --git a/src/javascript/lib/core/processes/mailbox.js b/src/javascript/lib/core/processes/mailbox.js new file mode 100644 index 00000000..a5dc7e34 --- /dev/null +++ b/src/javascript/lib/core/processes/mailbox.js @@ -0,0 +1,28 @@ +"use strict"; + +/* @flow */ + +class Mailbox{ + constructor(){ + this.messages = []; + } + + deliver(message){ + this.messages.push(message); + return message; + } + + get(){ + return this.messages; + } + + isEmpty(){ + return this.messages.length === 0; + } + + removeAt(index){ + this.messages.splice(index, 1); + } +} + +export default Mailbox; diff --git a/src/javascript/lib/core/processes/process.js b/src/javascript/lib/core/processes/process.js new file mode 100644 index 00000000..a3b3b49f --- /dev/null +++ b/src/javascript/lib/core/processes/process.js @@ -0,0 +1,141 @@ +'use strict'; + +/* @flow */ +import Mailbox from './mailbox'; +import ProcessSystem from './process_system'; +import States from './states'; + +function is_sleep(value) { + return Array.isArray(value) && value[0] === States.SLEEP; +} + +function is_receive(value) { + return Array.isArray(value) && value[0] === States.RECEIVE; +} + +function receive_timed_out(value) { + return value[2] != null && value[2] < Date.now(); +} + +class Process { + constructor(pid, func, args, mailbox, system) { + this.pid = pid; + this.func = func; + this.args = args; + this.mailbox = mailbox; + this.system = system; + this.status = States.STOPPED; + this.dict = {}; + this.flags = {}; + this.monitors = []; + } + + start() { + const function_scope = this; + let machine = this.main(); + + this.system.schedule(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, machine.next()); + }, this.pid); + } + + *main() { + let retval = States.NORMAL; + + try { + yield* this.func.apply(null, this.args); + } catch (e) { + console.error(e); + retval = e; + } + + this.system.exit(retval); + } + + process_flag(flag, value) { + const old_value = this.flags[flag]; + this.flags[flag] = value; + return old_value; + } + + is_trapping_exits() { + return ( + this.flags[Symbol.for('trap_exit')] && + this.flags[Symbol.for('trap_exit')] == true + ); + } + + signal(reason) { + if (reason !== States.NORMAL) { + console.error(reason); + } + + this.system.remove_proc(this.pid, reason); + } + + *receive(clauses) { + const messages = this.mailbox.get(); + + for (let i = 0; i < messages.length; i++) { + for (const clause of clauses) { + const value = yield* Patterns.match_or_default_gen( + clause.pattern, + messages[i], + clause.guard, + States.NOMATCH + ); + + if (value !== States.NOMATCH) { + this.mailbox.removeAt(i); + return clause.fn.apply(null, value); + } + } + } + + return States.NOMATCH; + } + + run(machine, step) { + const function_scope = this; + + if (!step.done) { + let value = step.value; + + if (is_sleep(value)) { + this.system.delay(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, machine.next()); + }, value[1]); + } else if (is_receive(value) && receive_timed_out(value)) { + let result = value[3](); + + this.system.schedule(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, machine.next(result)); + }); + } else if (is_receive(value)) { + let result = function_scope.receive(value[1]); + + if (result === States.NOMATCH) { + this.system.suspend(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, step); + }); + } else { + this.system.schedule(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, machine.next(result)); + }); + } + } else { + this.system.schedule(function() { + function_scope.system.set_current(function_scope.pid); + function_scope.run(machine, machine.next(value)); + }); + } + } + } +} + +export default Process; diff --git a/src/javascript/lib/core/processes/process_system.js b/src/javascript/lib/core/processes/process_system.js new file mode 100644 index 00000000..24562342 --- /dev/null +++ b/src/javascript/lib/core/processes/process_system.js @@ -0,0 +1,368 @@ +/* @flow */ +'use strict'; + +import Mailbox from './mailbox'; +import Process from './process'; +import States from './states'; +import Scheduler from './scheduler'; +import ErlangTypes from 'erlang-types'; + +class ProcessSystem { + constructor() { + this.pids = new Map(); + this.mailboxes = new Map(); + this.names = new Map(); + this.links = new Map(); + this.monitors = new Map(); + + const throttle = 5; //ms between scheduled tasks + this.current_process = null; + this.scheduler = new Scheduler(throttle); + this.suspended = new Map(); + + let process_system_scope = this; + this.main_process_pid = this.spawn(function*() { + yield process_system_scope.sleep(Symbol.for('Infinity')); + }); + this.set_current(this.main_process_pid); + } + + static *run(fun, args, context = null) { + if (fun.constructor.name === 'GeneratorFunction') { + return yield* fun.apply(context, args); + } else { + return yield fun.apply(context, args); + } + } + + spawn(...args) { + if (args.length === 1) { + let fun = args[0]; + return this.add_proc(fun, [], false).pid; + } else { + let mod = args[0]; + let fun = args[1]; + let the_args = args[2]; + + return this.add_proc(mod[fun], the_args, false, false).pid; + } + } + + spawn_link(...args) { + if (args.length === 1) { + let fun = args[0]; + return this.add_proc(fun, [], true, false).pid; + } else { + let mod = args[0]; + let fun = args[1]; + let the_args = args[2]; + + return this.add_proc(mod[fun], the_args, true, false).pid; + } + } + + link(pid) { + this.links.get(this.pid()).add(pid); + this.links.get(pid).add(this.pid()); + } + + unlink(pid) { + this.links.get(this.pid()).delete(pid); + this.links.get(pid).delete(this.pid()); + } + + spawn_monitor(...args) { + if (args.length === 1) { + let fun = args[0]; + let process = this.add_proc(fun, [], false, true); + return [process.pid, process.monitors[0]]; + } else { + let mod = args[0]; + let fun = args[1]; + let the_args = args[2]; + let process = this.add_proc(mod[fun], the_args, false, true); + + return [process.pid, process.monitors[0]]; + } + } + + monitor(pid) { + const real_pid = this.pidof(pid); + const ref = this.make_ref(); + + if (real_pid) { + this.monitors.set(ref, { + monitor: this.current_process.pid, + monitee: real_pid + }); + this.pids.get(real_pid).monitors.push(ref); + return ref; + } else { + this.send( + this.current_process.pid, + new ErlangTypes.Tuple('DOWN', ref, pid, real_pid, Symbol.for('noproc')) + ); + return ref; + } + } + + demonitor(ref) { + if (this.monitor.has(ref)) { + this.monitor.delete(ref); + return true; + } + + return false; + } + + set_current(id) { + let pid = this.pidof(id); + if (pid !== null) { + this.current_process = this.pids.get(pid); + this.current_process.status = States.RUNNING; + } + } + + add_proc(fun, args, linked, monitored) { + let newpid = new ErlangTypes.PID(); + let mailbox = new Mailbox(); + let newproc = new Process(newpid, fun, args, mailbox, this); + + this.pids.set(newpid, newproc); + this.mailboxes.set(newpid, mailbox); + this.links.set(newpid, new Set()); + + if (linked) { + this.link(newpid); + } + + if (monitored) { + this.monitor(newpid); + } + + newproc.start(); + return newproc; + } + + remove_proc(pid, exitreason) { + this.pids.delete(pid); + this.unregister(pid); + this.scheduler.removePid(pid); + + if (this.links.has(pid)) { + for (let linkpid of this.links.get(pid)) { + this.exit(linkpid, exitreason); + this.links.get(linkpid).delete(pid); + } + + this.links.delete(pid); + } + } + + register(name, pid) { + if (!this.names.has(name)) { + this.names.set(name, pid); + } else { + throw new Error('Name is already registered to another process'); + } + } + + whereis(name) { + return this.names.has(name) ? this.names.get(name) : null; + } + + registered() { + return this.names.keys(); + } + + unregister(pid) { + for (let name of this.names.keys()) { + if (this.names.has(name) && this.names.get(name) === pid) { + this.names.delete(name); + } + } + } + + pid() { + return this.current_process.pid; + } + + pidof(id) { + if (id instanceof ErlangTypes.PID) { + return this.pids.has(id) ? id : null; + } else if (id instanceof Process) { + return id.pid; + } else { + let pid = this.whereis(id); + if (pid === null) + throw 'Process name not registered: ' + id + ' (' + typeof id + ')'; + return pid; + } + } + + send(id, msg) { + const pid = this.pidof(id); + + if (pid) { + this.mailboxes.get(pid).deliver(msg); + + if (this.suspended.has(pid)) { + let fun = this.suspended.get(pid); + this.suspended.delete(pid); + this.schedule(fun); + } + } + + return msg; + } + + receive(args, timeout = 0, timeoutFn = () => true) { + let DateTimeout = null; + + if (timeout === 0 || timeout === Infinity) { + DateTimeout = null; + } else { + DateTimeout = Date.now() + timeout; + } + + return [States.RECEIVE, args, DateTimeout, timeoutFn]; + } + + sleep(duration) { + return [States.SLEEP, duration]; + } + + suspend(fun) { + this.current_process.status = States.SUSPENDED; + this.suspended.set(this.current_process.pid, fun); + } + + delay(fun, time) { + this.current_process.status = States.SLEEPING; + + if (Number.isInteger(time)) { + this.scheduler.scheduleFuture(this.current_process.pid, time, fun); + } + } + + schedule(fun, pid) { + const the_pid = pid != null ? pid : this.current_process.pid; + this.scheduler.schedule(the_pid, fun); + } + + exit(one, two) { + let pid = null; + let reason = null; + let process = null; + + if (two) { + pid = one; + reason = two; + process = this.pids.get(this.pidof(pid)); + + if ( + (process && process.is_trapping_exits()) || + reason === States.KILL || + reason === States.NORMAL + ) { + this.mailboxes + .get(process.pid) + .deliver(new ErlangTypes.Tuple(States.EXIT, this.pid(), reason)); + } else { + process.signal(reason); + } + } else { + pid = this.current_process.pid; + reason = one; + process = this.current_process; + + process.signal(reason); + } + + for (let ref in process.monitors) { + let mons = this.monitors.get(ref); + this.send( + mons['monitor'], + new ErlangTypes.Tuple( + 'DOWN', + ref, + mons['monitee'], + mons['monitee'], + reason + ) + ); + } + } + + error(reason) { + this.current_process.signal(reason); + } + + process_flag(...args) { + if (args.length == 2) { + const flag = args[0]; + const value = args[1]; + return this.current_process.process_flag(flag, value); + } else { + const pid = this.pidof(args[0]); + const flag = args[1]; + const value = args[2]; + return this.pids.get(pid).process_flag(flag, value); + } + } + + put(key, value) { + this.current_process.dict[key] = value; + } + + get_process_dict() { + return this.current_process.dict; + } + + get(key, default_value = null) { + if (key in this.current_process.dict) { + return this.current_process.dict[key]; + } else { + return default_value; + } + } + + get_keys(value) { + if (value) { + let keys = []; + + for (let key of Object.keys(this.current_process.dict)) { + if (this.current_process.dict[key] === value) { + keys.push(key); + } + } + + return keys; + } + + return Object.keys(this.current_process.dict); + } + + erase(key) { + if (key != null) { + delete this.current_process.dict[key]; + } else { + this.current_process.dict = {}; + } + } + + is_alive(pid) { + const real_pid = this.pidof(pid); + return real_pid != null; + } + + list() { + return Array.from(this.pids.keys()); + } + + make_ref() { + return new ErlangTypes.Reference(); + } +} + +export default ProcessSystem; diff --git a/src/javascript/lib/core/processes/scheduler.js b/src/javascript/lib/core/processes/scheduler.js new file mode 100644 index 00000000..4a762008 --- /dev/null +++ b/src/javascript/lib/core/processes/scheduler.js @@ -0,0 +1,104 @@ +"use strict"; + +class ProcessQueue { + constructor(pid){ + this.pid = pid; + this.tasks = []; + } + + empty(){ + return this.tasks.length === 0; + } + + add(task){ + this.tasks.push(task); + } + + next(){ + return this.tasks.shift(); + } +} + +class Scheduler { + constructor(throttle = 0, reductions_per_process = 8){ + this.isRunning = false; + this.invokeLater = function (callback) { setTimeout(callback, throttle); }; + + // In our case a reduction is equal to a task call + // Controls how many tasks are called at a time per process + this.reductions_per_process = reductions_per_process; + this.queues = new Map(); + this.run(); + } + + addToQueue(pid, task){ + if(!this.queues.has(pid)){ + this.queues.set(pid, new ProcessQueue(pid)); + } + + this.queues.get(pid).add(task); + } + + removePid(pid){ + this.isRunning = true; + + this.queues.delete(pid); + + this.isRunning = false; + } + + run(){ + if (this.isRunning) { + this.invokeLater(() => { this.run(); }); + } else { + for(let [pid, queue] of this.queues){ + let reductions = 0; + while(queue && !queue.empty() && reductions < this.reductions_per_process){ + let task = queue.next(); + this.isRunning = true; + + let result; + + try{ + result = task(); + }catch(e){ + console.error(e); + result = e; + } + + this.isRunning = false; + + if (result instanceof Error) { + throw result; + } + + reductions++; + } + } + + this.invokeLater(() => { this.run(); }); + } + } + + addToScheduler(pid, task, dueTime = 0) { + if(dueTime === 0){ + this.invokeLater(() => { + this.addToQueue(pid, task); + }); + }else{ + setTimeout(() => { + this.addToQueue(pid, task); + }, dueTime); + } + }; + + schedule(pid, task){ + this.addToScheduler(pid, () => { task(); }); + } + + scheduleFuture(pid, dueTime, task){ + this.addToScheduler(pid, () => { task(); }, dueTime); + } +} + +export default Scheduler; diff --git a/src/javascript/lib/core/processes/states.js b/src/javascript/lib/core/processes/states.js new file mode 100644 index 00000000..24294ecd --- /dev/null +++ b/src/javascript/lib/core/processes/states.js @@ -0,0 +1,15 @@ +export default { + NORMAL: Symbol.for("normal"), + KILL: Symbol.for("kill"), + SUSPEND: Symbol.for("suspend"), + CONTINUE: Symbol.for("continue"), + RECEIVE: Symbol.for("receive"), + SEND: Symbol.for("send"), + SLEEPING: Symbol.for("sleeping"), + RUNNING: Symbol.for("running"), + SUSPENDED: Symbol.for("suspended"), + STOPPED: Symbol.for("stopped"), + SLEEP: Symbol.for("sleep"), + EXIT: Symbol.for("exit"), + NOMATCH: Symbol.for("no_match") +} \ No newline at end of file diff --git a/src/javascript/lib/core/protocol.js b/src/javascript/lib/core/protocol.js index 15466ed8..a7cc3d38 100644 --- a/src/javascript/lib/core/protocol.js +++ b/src/javascript/lib/core/protocol.js @@ -7,13 +7,16 @@ class Protocol { this.fallback = null; function createFun(funName) { - return function (...args) { + return function*(...args) { const thing = args[0]; let fun = null; if (thing === null && this.hasImplementation(Symbol('null'))) { fun = this.registry.get(Symbol)[funName]; - } else if (Number.isInteger(thing) && this.hasImplementation(Core.Integer)) { + } else if ( + Number.isInteger(thing) && + this.hasImplementation(Core.Integer) + ) { fun = this.registry.get(Core.Integer)[funName]; } else if ( typeof thing === 'number' && @@ -21,7 +24,10 @@ class Protocol { this.hasImplementation(Core.Float) ) { fun = this.registry.get(Core.Float)[funName]; - } else if (typeof thing === 'string' && this.hasImplementation(Core.BitString)) { + } else if ( + typeof thing === 'string' && + this.hasImplementation(Core.BitString) + ) { fun = this.registry.get(Core.BitString)[funName]; } else if ( thing && @@ -29,7 +35,9 @@ class Protocol { thing.has(Symbol.for('__struct__')) && this.hasImplementation(thing) ) { - fun = this.registry.get(thing.get(Symbol.for('__struct__')).__MODULE__)[funName]; + fun = this.registry.get( + thing.get(Symbol.for('__struct__')).__MODULE__ + )[funName]; } else if (thing !== null && this.hasImplementation(thing)) { fun = this.registry.get(thing.constructor)[funName]; } else if (this.fallback) { @@ -37,7 +45,7 @@ class Protocol { } if (fun != null) { - const retval = fun.apply(this, args); + const retval = yield* fun.apply(this, args); return retval; } @@ -59,9 +67,17 @@ class Protocol { } hasImplementation(thing) { - if (thing === Core.Integer || thing === Core.Float || thing === Core.BitString) { + if ( + thing === Core.Integer || + thing === Core.Float || + thing === Core.BitString + ) { return this.registry.has(thing); - } else if (thing && thing instanceof Map && thing.has(Symbol.for('__struct__'))) { + } else if ( + thing && + thing instanceof Map && + thing.has(Symbol.for('__struct__')) + ) { return this.registry.has(thing.get(Symbol.for('__struct__')).__MODULE__); } diff --git a/src/javascript/lib/core/special_forms.js b/src/javascript/lib/core/special_forms.js index 8caa2dfe..db2845d9 100644 --- a/src/javascript/lib/core/special_forms.js +++ b/src/javascript/lib/core/special_forms.js @@ -1,13 +1,13 @@ import Core from '../core'; -function _case(condition, clauses) { - return Core.Patterns.defmatch(...clauses)(condition); +function* _case(condition, clauses) { + return yield* Core.Patterns.defmatchGen(...clauses)(condition); } -function cond(...clauses) { +function* cond(...clauses) { for (const clause of clauses) { if (clause[0]) { - return clause[1](); + return yield* clause[1](); } } @@ -35,7 +35,7 @@ function run_list_generators(generator, generators) { return run_list_generators(next_gen, generators); } -function _for(expression, generators, collectable_protocol, into = []) { +function* _for(expression, generators, collectable_protocol, into = []) { const [result, fun] = collectable_protocol.into(into); let accumulatingResult = result; @@ -43,7 +43,7 @@ function _for(expression, generators, collectable_protocol, into = []) { for (const value of generatedValues) { if (expression.guard.apply(this, value)) { - accumulatingResult = fun( + accumulatingResult = yield* fun( accumulatingResult, new Core.Tuple(Symbol.for('cont'), expression.fn.apply(this, value)), ); @@ -53,17 +53,17 @@ function _for(expression, generators, collectable_protocol, into = []) { return fun(accumulatingResult, Symbol.for('done')); } -function _try(do_fun, rescue_function, catch_fun, else_function, after_function) { +function* _try(do_fun, rescue_function, catch_fun, else_function, after_function) { let result = null; try { - result = do_fun(); + result = yield* do_fun(); } catch (e) { let ex_result = null; if (rescue_function) { try { - ex_result = rescue_function(e); + ex_result = yield* rescue_function(e); return ex_result; } catch (ex) { if (ex instanceof Core.Patterns.MatchError) { @@ -74,7 +74,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) if (catch_fun) { try { - ex_result = catch_fun(e); + ex_result = yield* catch_fun(e); return ex_result; } catch (ex) { if (ex instanceof Core.Patterns.MatchError) { @@ -86,7 +86,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) throw e; } finally { if (after_function) { - after_function(); + yield* after_function(); } } @@ -105,7 +105,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) } } -function _with(...args) { +function* _with(...args) { let argsToPass = []; let successFunction = null; let elseFunction = null; @@ -119,9 +119,9 @@ function _with(...args) { for (let i = 0; i < args.length; i++) { const [pattern, func] = args[i]; - const result = func(...argsToPass); + const result = yield* func(...argsToPass); - const patternResult = Core.Patterns.match_or_default(pattern, result); + const patternResult = yield* Core.Patterns.match_or_default_gen(pattern, result); if (patternResult == null) { if (elseFunction) { @@ -136,29 +136,8 @@ function _with(...args) { return successFunction(...argsToPass); } -function receive(clauses, timeout = 0, timeoutFn = () => true) { - console.warn('Receive not supported'); - - const messages = []; // this.mailbox.get(); - const NOMATCH = Symbol('NOMATCH'); - - for (let i = 0; i < messages.length; i++) { - for (const clause of clauses) { - const value = Core.Patterns.match_or_default( - clause.pattern, - messages[i], - clause.guard, - NOMATCH, - ); - - if (value !== NOMATCH) { - this.mailbox.removeAt(i); - return clause.fn.apply(null, value); - } - } - } - - return null; +function* receive(clauses, timeout = 0, timeoutFn = () => true) { + return yield* Core.global.__elxirscript_process_system__.receive(clauses, timeout, timeoutFn); } export default { diff --git a/src/javascript/tests/case.spec.js b/src/javascript/tests/case.spec.js index 8715ae11..b48d3304 100644 --- a/src/javascript/tests/case.spec.js +++ b/src/javascript/tests/case.spec.js @@ -5,7 +5,7 @@ const Patterns = Core.Patterns; const SpecialForms = Core.SpecialForms; const Tuple = Core.Tuple; -test('case', (t) => { +test('case', async (t) => { const clauses = [ Patterns.clause( [new Tuple(Symbol.for('selector'), Patterns.variable(), Patterns.variable())], @@ -15,7 +15,7 @@ test('case', (t) => { Patterns.clause([Patterns.variable()], value => value), ]; - const result = SpecialForms._case('thing', clauses); + const result = await SpecialForms._case('thing', clauses); t.is(result, 'thing'); }); diff --git a/src/javascript/tests/cond.spec.js b/src/javascript/tests/cond.spec.js index f1a09736..4ee5afb8 100644 --- a/src/javascript/tests/cond.spec.js +++ b/src/javascript/tests/cond.spec.js @@ -3,14 +3,14 @@ import Core from '../lib/core'; const SpecialForms = Core.SpecialForms; -test('cond', (t) => { +test('cond', async (t) => { const clauses = [ [1 + 1 === 1, () => 'This will never match'], [2 * 2 !== 4, () => 'Nor this'], [true, () => 'This will'], ]; - const result = SpecialForms.cond(...clauses); + const result = await SpecialForms.cond(...clauses); t.is(result, 'This will'); }); diff --git a/src/javascript/tests/core/erlang_compat/lists_spec.js b/src/javascript/tests/core/erlang_compat/lists_spec.js index 7e943b73..c7d4622f 100644 --- a/src/javascript/tests/core/erlang_compat/lists_spec.js +++ b/src/javascript/tests/core/erlang_compat/lists_spec.js @@ -16,12 +16,12 @@ test('flatten', (t) => { t.deepEqual(Core.lists.flatten([1, [[2], 3]]), [1, 2, 3]); }); -test('foldl', (t) => { - t.deepEqual(Core.lists.foldl((v, acc) => acc + v, 0, [1, 2, 3]), 6); +test('foldl', async (t) => { + t.deepEqual(await Core.lists.foldl((v, acc) => acc + v, 0, [1, 2, 3]), 6); }); -test('foldr', (t) => { - t.deepEqual(Core.lists.foldr((v, acc) => acc + v.toString(), '', [1, 2, 3]), '321'); +test('foldr', async (t) => { + t.deepEqual(await Core.lists.foldr((v, acc) => acc + v.toString(), '', [1, 2, 3]), '321'); }); test('member/2', (t) => { diff --git a/src/javascript/tests/core/erlang_compat/maps_spec.js b/src/javascript/tests/core/erlang_compat/maps_spec.js index 1a504227..7e1d1539 100644 --- a/src/javascript/tests/core/erlang_compat/maps_spec.js +++ b/src/javascript/tests/core/erlang_compat/maps_spec.js @@ -23,9 +23,9 @@ test('find', (t) => { t.deepEqual(result.values, [Symbol.for('ok'), 'b']); }); -test('fold', (t) => { +test('fold', async (t) => { const myMap = new Map([['a', 1], ['b', 2]]); - const result = Core.maps.fold((k, v, acc) => acc + v, 0, myMap); + const result = await Core.maps.fold((k, v, acc) => acc + v, 0, myMap); t.is(result, 3); }); diff --git a/src/javascript/tests/core/functions.spec.js b/src/javascript/tests/core/functions.spec.js index d3ed14e1..f56d312d 100644 --- a/src/javascript/tests/core/functions.spec.js +++ b/src/javascript/tests/core/functions.spec.js @@ -3,15 +3,15 @@ import Core from '../../lib/core'; const Functions = Core.Functions; -test('call_property', (t) => { - t.is(Functions.call_property(1, 'toString'), '1'); - t.is(Functions.call_property([], 'toString'), ''); - t.is(Functions.call_property([], 'length'), 0); - t.is(Functions.call_property('', 'toString'), ''); - t.is(Functions.call_property('', 'length'), 0); - t.is(Functions.call_property(Symbol('test'), 'toString'), 'Symbol(test)'); - t.is(Functions.call_property({ completed: false }, 'completed'), false); - t.is(Functions.call_property({ id: 0 }, 'id'), 0); +test('call_property', async (t) => { + t.is(await Functions.call_property(1, 'toString'), '1'); + t.is(await Functions.call_property([], 'toString'), ''); + t.is(await Functions.call_property([], 'length'), 0); + t.is(await Functions.call_property('', 'toString'), ''); + t.is(await Functions.call_property('', 'length'), 0); + t.is(await Functions.call_property(Symbol('test'), 'toString'), 'Symbol(test)'); + t.is(await Functions.call_property({ completed: false }, 'completed'), false); + t.is(await Functions.call_property({ id: 0 }, 'id'), 0); }); test('split_at', (t) => { diff --git a/src/javascript/tests/core/processes.spec.js b/src/javascript/tests/core/processes.spec.js new file mode 100644 index 00000000..969c66ef --- /dev/null +++ b/src/javascript/tests/core/processes.spec.js @@ -0,0 +1,44 @@ +import test from 'ava'; +import Patterns from 'tailored'; +import ProcessSystem from '../../lib/core/processes/process_system'; + +let system = null; + +test.beforeEach(() => { + system = new ProcessSystem(); +}); + +test('spawn linked process', async (t) => { + // spawn one process + const pid1 = system.spawn_link(async () => { + const arg = Patterns.clause([Patterns.variable()], async x => console.log(x)); + + await system.pause(); + console.log(`first process ${system.pid()}`); + await system.receive([arg]); + console.log(`first process ${system.pid()}`); + await Math.log2(2); + await system.pause(); + console.log(`first process ${system.pid()}`); + await Math.log2(4); + }); + + // spawn another process + const pid2 = system.spawn_link(async () => { + await system.pause(); + console.log(`second process ${system.pid()}`); + await Math.log2(4); + await system.pause(); + console.log(`second process ${system.pid()}`); + await system.send(pid1, 'This message was sent'); + console.log(`second process ${system.pid()}`); + await Math.log2(4); + }); + + console.log(`first process pid should be ${pid1}`); + console.log(`first process pid should be ${pid2}`); + + t.is(system.list().length, 3); + t.is(system.list()[1], pid1); + t.is(system.list()[2], pid2); +}); diff --git a/src/javascript/tests/for.spec.js b/src/javascript/tests/for.spec.js index 9bcd1894..4220d08f 100644 --- a/src/javascript/tests/for.spec.js +++ b/src/javascript/tests/for.spec.js @@ -27,19 +27,19 @@ const collectable = { }, }; -test('simple for', (t) => { +test('simple for', async (t) => { const gen = Patterns.list_generator($, [1, 2, 3, 4]); - const result = SpecialForms._for(Patterns.clause([$], x => x * 2), [gen], collectable); + const result = await SpecialForms._for(Patterns.clause([$], x => x * 2), [gen], collectable); t.deepEqual(result, [2, 4, 6, 8]); }); -test('for with multiple generators', (t) => { +test('for with multiple generators', async (t) => { // for x <- [1, 2], y <- [2, 3], do: x*y const gen = Patterns.list_generator($, [1, 2]); const gen2 = Patterns.list_generator($, [2, 3]); - const result = SpecialForms._for( + const result = await SpecialForms._for( Patterns.clause([$, $], (x, y) => x * y), [gen, gen2], collectable, @@ -48,10 +48,10 @@ test('for with multiple generators', (t) => { t.deepEqual(result, [2, 3, 4, 6]); }); -test('for with filter', (t) => { +test('for with filter', async (t) => { // for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n const gen = Patterns.list_generator($, [1, 2, 3, 4, 5, 6]); - const result = SpecialForms._for( + const result = await SpecialForms._for( Patterns.clause([$], x => x, x => x % 2 === 0), [gen], collectable, @@ -60,7 +60,7 @@ test('for with filter', (t) => { t.deepEqual(result, [2, 4, 6]); }); -test('for with pattern matching', (t) => { +test('for with pattern matching', async (t) => { // for {:user, name} <- [user: "john", admin: "john", user: "meg"], do // String.upcase(name) // end @@ -70,7 +70,7 @@ test('for with pattern matching', (t) => { [[Symbol.for('user'), 'john'], [Symbol.for('admin'), 'john'], [Symbol.for('user'), 'meg']], ); - const result = SpecialForms._for( + const result = await SpecialForms._for( Patterns.clause([[Symbol.for('user'), $]], name => name.toUpperCase()), [gen], collectable, @@ -79,7 +79,7 @@ test('for with pattern matching', (t) => { t.deepEqual(result, ['JOHN', 'MEG']); }); -test('for with bitstring', (t) => { +test('for with bitstring', async (t) => { // for <> >>, do: {r, g, b} const gen = Patterns.bitstring_generator( @@ -115,7 +115,7 @@ test('for with bitstring', (t) => { (r, g, b) => new Tuple(r, g, b), ); - const result = SpecialForms._for(expression, [gen], collectable); + const result = await SpecialForms._for(expression, [gen], collectable); t.deepEqual(result, [ new Tuple(213, 45, 132), diff --git a/src/javascript/tests/try.spec.js b/src/javascript/tests/try.spec.js index 97150618..3ce7c7b7 100644 --- a/src/javascript/tests/try.spec.js +++ b/src/javascript/tests/try.spec.js @@ -4,7 +4,7 @@ import Core from '../lib/core'; const Patterns = Core.Patterns; const SpecialForms = Core.SpecialForms; -test('try', (t) => { +test('try', async (t) => { /* try do 1 / x @@ -19,7 +19,7 @@ test('try', (t) => { const x = 1; - const value = SpecialForms._try( + const value = await SpecialForms._try( () => 1 / x, null, null, diff --git a/src/javascript/tests/with.spec.js b/src/javascript/tests/with.spec.js index 0ac09ced..8e56e81c 100644 --- a/src/javascript/tests/with.spec.js +++ b/src/javascript/tests/with.spec.js @@ -16,7 +16,7 @@ function map_fetch(map, key) { return Symbol.for('error'); } -test('with', (t) => { +test('with', async (t) => { /* opts = %{width: 10, height: 15} @@ -29,7 +29,7 @@ test('with', (t) => { const opts = { width: 10, height: 15 }; - const value = SpecialForms._with( + const value = await SpecialForms._with( [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'width')], [new Tuple(Symbol.for('ok'), $), width => map_fetch(opts, 'height')], (width, height) => new Tuple(Symbol.for('ok'), width * height), @@ -38,7 +38,7 @@ test('with', (t) => { t.deepEqual(value, new Tuple(Symbol.for('ok'), 150)); }); -test('with without match', (t) => { +test('with without match', async (t) => { /* opts = %{width: 10} @@ -51,7 +51,7 @@ test('with without match', (t) => { const opts = { width: 10 }; - const value = SpecialForms._with( + const value = await SpecialForms._with( [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'width')], [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'height')], (width, height) => new Tuple(Symbol.for('ok'), width * height), @@ -60,7 +60,7 @@ test('with without match', (t) => { t.deepEqual(value, Symbol.for('error')); }); -test('with bare expression', (t) => { +test('with bare expression', async (t) => { /* opts = %{width: 10} @@ -74,7 +74,7 @@ test('with bare expression', (t) => { const opts = { width: 10, height: 15 }; - const value = SpecialForms._with( + const value = await SpecialForms._with( [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'width')], [$, width => width * 2], [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'height')], @@ -84,7 +84,7 @@ test('with bare expression', (t) => { t.deepEqual(value, new Tuple(Symbol.for('ok'), 300)); }); -test('with else', (t) => { +test('with else', async (t) => { /* opts = %{width: 10} @@ -100,7 +100,7 @@ test('with else', (t) => { const opts = { width: 10 }; - const value = SpecialForms._with( + const value = await SpecialForms._with( [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'width')], [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'height')], (width, height) => new Tuple(Symbol.for('ok'), width * height), @@ -115,7 +115,7 @@ test('with else', (t) => { t.deepEqual(value, new Tuple(Symbol.for('error'), Symbol.for('wrong_data'))); }); -test('with else that don`t match', (t) => { +test('with else that don`t match', async (t) => { /* opts = %{width: 10} @@ -131,8 +131,7 @@ test('with else that don`t match', (t) => { const opts = { width: 10 }; - const withFunction = SpecialForms._with.bind( - null, + const withFunctionPromise = SpecialForms._with( [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'width')], [new Tuple(Symbol.for('ok'), $), () => map_fetch(opts, 'height')], (width, height) => new Tuple(Symbol.for('ok'), width * height), @@ -144,5 +143,5 @@ test('with else that don`t match', (t) => { ), ); - t.throws(withFunction, MatchError); + await t.throws(withFunctionPromise, MatchError); }); diff --git a/test/support/integration/main.ex b/test/support/integration/main.ex new file mode 100644 index 00000000..686b86bd --- /dev/null +++ b/test/support/integration/main.ex @@ -0,0 +1,6 @@ +defmodule ElixirScript.Integration.Main do + + def start(_, _) do + :console.log("hi") + end +end diff --git a/yarn.lock b/yarn.lock index 9aec577d..29eb1c28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3846,9 +3846,8 @@ table@^4.0.1: slice-ansi "0.0.4" string-width "^2.0.0" -tailored@^2.7.4: +"tailored@file:/Users/bryanjos/projects/basstype/tailored": version "2.7.4" - resolved "https://registry.yarnpkg.com/tailored/-/tailored-2.7.4.tgz#473c6c91fcaeb2c8e4ddd5c2bddb33190ac21780" dependencies: erlang-types "^1.0.1"