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 af9f1b2c..689a2b0c 100644 --- a/lib/elixir_script/passes/output/js_module.ex +++ b/lib/elixir_script/passes/output/js_module.ex @@ -17,29 +17,37 @@ defmodule ElixirScript.Output.JSModule do end def start do - normal = Helpers.symbol("normal") + start_process_call = Helpers.call( + 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( J.identifier("Elixir"), J.identifier("start") ), - Helpers.function( + Helpers.arrow_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 ]) ) ) @@ -51,11 +59,11 @@ defmodule ElixirScript.Output.JSModule do J.identifier("Elixir"), J.identifier("load") ), - Helpers.function( + Helpers.arrow_function( [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/form.ex b/lib/elixir_script/passes/translate/form.ex index 21972d5e..09f6c309 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 @js_reserved_words [ @@ -61,7 +60,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") @@ -169,17 +168,17 @@ defmodule ElixirScript.Translate.Form do end def compile({:case, _, [condition, [do: clauses]]}, state) do - func = Helpers.call( + ast = Helpers.call( 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 } 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..adcf8e88 100644 --- a/lib/elixir_script/passes/translate/forms/for.ex +++ b/lib/elixir_script/passes/translate/forms/for.ex @@ -25,7 +25,7 @@ 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")] ) 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/receive.ex b/lib/elixir_script/passes/translate/forms/receive.ex index 7a7f22cd..1913ee45 100644 --- a/lib/elixir_script/passes/translate/forms/receive.ex +++ b/lib/elixir_script/passes/translate/forms/receive.ex @@ -1,26 +1,48 @@ defmodule ElixirScript.Translate.Forms.Receive do @moduledoc false alias ESTree.Tools.Builder, as: J - alias ElixirScript.Translate.Helpers + alias ElixirScript.Translate.{Helpers, Form, Function, Clause} @doc """ receive is not supported just yet, but we compile it to a stub function for now """ def compile(blocks, state) do - _receive_block = Keyword.get(blocks, :do) - _after_block = Keyword.get(blocks, :after, nil) + receive_block = Keyword.get(blocks, :do) + after_block = Keyword.get(blocks, :after, nil) + + receive_block = Enum.map(receive_block, fn x -> Clause.compile(x, state) |> elem(0) end) + |> List.flatten + |> J.array_expression() receive_function = J.member_expression( Helpers.special_forms(), J.identifier("receive") ) + args = [receive_block] ++ process_after(after_block, state) + ast = Helpers.call( receive_function, - [] + args ) { ast, state } end + + defp process_after(nil, _) do + [] + end + + defp process_after([{:->, _, [[timeout], body]}], state) do + timeout = Form.compile!(timeout, state) + {body, _state} = Function.compile_block(body, state) + + function = Helpers.arrow_function( + [], + J.block_statement(List.wrap(body)) + ) + + [timeout, function] + end end diff --git a/lib/elixir_script/passes/translate/forms/remote.ex b/lib/elixir_script/passes/translate/forms/remote.ex index 8052e041..108a13ba 100644 --- a/lib/elixir_script/passes/translate/forms/remote.ex +++ b/lib/elixir_script/passes/translate/forms/remote.ex @@ -85,14 +85,14 @@ defmodule ElixirScript.Translate.Forms.Remote do module === Elixir -> members = ["Elixir", "__load"] - Helpers.call( + Helpers.call_sync( Identifier.make_namespace_members(members), [J.identifier("Elixir")] ) 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..a00d9d7d 100644 --- a/lib/elixir_script/passes/translate/forms/try.ex +++ b/lib/elixir_script/passes/translate/forms/try.ex @@ -70,7 +70,7 @@ defmodule ElixirScript.Translate.Forms.Try do Helpers.call( JS.member_expression( Helpers.patterns(), - JS.identifier("defmatch") + JS.identifier("defmatchAsync") ), processed_clauses ) diff --git a/lib/elixir_script/passes/translate/function.ex b/lib/elixir_script/passes/translate/function.ex index 729684f3..745fb6cd 100644 --- a/lib/elixir_script/passes/translate/function.ex +++ b/lib/elixir_script/passes/translate/function.ex @@ -98,7 +98,7 @@ defmodule ElixirScript.Translate.Function do match_or_default_call = Helpers.call( J.member_expression( Helpers.patterns(), - J.identifier("match_or_default") + J.identifier("match_or_default_async") ), [J.array_expression(patterns), J.identifier("__function_args__"), guards] ) @@ -137,7 +137,7 @@ defmodule ElixirScript.Translate.Function do |> Clause.return_last_statement |> 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} @@ -159,7 +159,10 @@ 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) -> + {ast, acc} = Form.compile(x, acc) + {[pause(), ast], acc} + end) List.flatten(list) _ -> Form.compile!(block, state) @@ -168,7 +171,17 @@ defmodule ElixirScript.Translate.Function do {ast, state} end - def update_last_call(clause_body, %{function: {name, _}, anonymous_fn: anonymous?}) do + defp pause() do + Helpers.call( + J.member_expression( + Helpers.process_system, + J.identifier("pause") + ), + [] + ) + end + + defp update_last_call(clause_body, %{function: {name, _}, anonymous_fn: anonymous?}) do last_item = List.last(clause_body) function_name = ElixirScript.Translate.Identifier.make_function_name(name) @@ -191,7 +204,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") @@ -213,7 +226,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..424edc40 100644 --- a/lib/elixir_script/passes/translate/helpers.ex +++ b/lib/elixir_script/passes/translate/helpers.ex @@ -19,18 +19,26 @@ defmodule ElixirScript.Translate.Helpers do ) end - def call(callee, arguments) do + def call_sync(callee, arguments) do J.call_expression( callee, arguments ) end + def call(callee, arguments) do + call_sync(callee, arguments) + |> J.await_expression() + end + def arrow_function(params, body) do J.arrow_function_expression( params, [], - body + body, + false, + false, + true ) end @@ -39,7 +47,10 @@ defmodule ElixirScript.Translate.Helpers do name, params, [], - body + body, + false, + false, + true ) end @@ -89,6 +100,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/src/javascript/lib/core.js b/src/javascript/lib/core.js index 2b3e9489..49bf03cc 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(); diff --git a/src/javascript/lib/core/erlang_compat/lists.js b/src/javascript/lib/core/erlang_compat/lists.js index 066f903a..c4ed3901 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)); +async function foreach(fun, list) { + for (const x of list) { + await fun(x); + } return Symbol.for('ok'); } @@ -33,11 +35,17 @@ function flatten(deepList, tail = []) { return val.concat(tail); } -function foldl(fun, acc0, list) { - return list.reduce((acc, value) => fun(value, acc), acc0); +async function foldl(fun, acc0, list) { + let acc = acc0; + + for (const value of list) { + acc = await fun(value, acc); + } + + return acc; } -function foldr(fun, acc0, list) { +async function foldr(fun, acc0, list) { return foldl(fun, acc0, reverse(list)); } @@ -124,12 +132,12 @@ function keytake(key, n, tupleList) { return false; } -function mapfoldl(fun, acc0, list1) { +async function mapfoldl(fun, acc0, list1) { const listResult = []; let accResult = acc0; for (const item of list1) { - const tuple = fun(item, accResult); + const tuple = await fun(item, accResult); listResult.push(tuple.get(0)); accResult = tuple.get(1); } @@ -141,19 +149,35 @@ function concat(things) { return things.map(v => v.toString()).join(); } -function map(fun, list) { - return list.map(value => fun(value)); +async function map(fun, list) { + const reList = []; + + for (const value of list) { + const result = await fun(value); + reList.push(result); + } + + return reList; } -function filter(pred, list1) { - return list1.filter(x => pred(x)); +async function filter(pred, list1) { + const reList = []; + + for (const value of list1) { + const result = await pred(value); + if (result === true) { + reList.push(value); + } + } + + return reList; } -function filtermap(fun, list1) { +async function filtermap(fun, list1) { const list2 = []; for (const item of list1) { - const value = fun(item); + const value = await fun(item); if (value === true) { list2.push(item); @@ -175,9 +199,9 @@ function member(elem, list) { return false; } -function all(pred, list) { +async function all(pred, list) { for (const item of list) { - if (pred(item) === false) { + if ((await pred(item)) === false) { return false; } } @@ -185,9 +209,9 @@ function all(pred, list) { return true; } -function any(pred, list) { +async function any(pred, list) { for (const item of list) { - if (pred(item) === true) { + if ((await pred(item)) === true) { return true; } } @@ -195,7 +219,7 @@ function any(pred, list) { return false; } -function splitwith(pred, list) { +async function splitwith(pred, list) { let switchToList2 = false; const list1 = []; const list2 = []; @@ -203,7 +227,7 @@ function splitwith(pred, list) { for (const item of list) { if (switchToList2 === true) { list2.push(item); - } else if (pred(item) === true) { + } else if ((await pred(item)) === true) { list1.push(item); } else { switchToList2 = true; @@ -214,7 +238,7 @@ function splitwith(pred, list) { return new ErlangTypes.Tuple(list1, list2); } -function sort(...args) { +async function sort(...args) { if (args.length === 1) { const list2 = [...args[0]]; return list2.sort(); @@ -223,15 +247,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(async (a, b) => { + const sortResult = await fun(a, b); - if (result === true) { + if (sortResult === true) { return -1; } return 1; }); + + return Promise.all(result); } export default { diff --git a/src/javascript/lib/core/erlang_compat/maps.js b/src/javascript/lib/core/erlang_compat/maps.js index 32dc3461..426b4fca 100644 --- a/src/javascript/lib/core/erlang_compat/maps.js +++ b/src/javascript/lib/core/erlang_compat/maps.js @@ -21,11 +21,11 @@ function find(key, map) { return ERROR; } -function fold(fun, init, map) { +async function fold(fun, init, map) { let acc = init; for (const [key, value] of map.entries()) { - acc = fun(key, value, acc); + acc = await fun(key, value, acc); } return acc; diff --git a/src/javascript/lib/core/functions.js b/src/javascript/lib/core/functions.js index b71719a8..78390058 100644 --- a/src/javascript/lib/core/functions.js +++ b/src/javascript/lib/core/functions.js @@ -4,7 +4,7 @@ import Core from '../core'; import proplists from './erlang_compat/proplists'; import erlang from './erlang_compat/erlang'; -function call_property(item, property) { +async function call_property(item, property) { if (!property) { if (item instanceof Function || typeof item === 'function') { return item(); @@ -153,11 +153,11 @@ class Recurse { } } -function trampoline(f) { +async function trampoline(f) { let currentValue = f; while (currentValue && currentValue instanceof Recurse) { - currentValue = currentValue.func(); + currentValue = await currentValue.func(); } return currentValue; diff --git a/src/javascript/lib/core/processes/mailbox.js b/src/javascript/lib/core/processes/mailbox.js new file mode 100644 index 00000000..0b5c422b --- /dev/null +++ b/src/javascript/lib/core/processes/mailbox.js @@ -0,0 +1,24 @@ +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..1b9aaec3 --- /dev/null +++ b/src/javascript/lib/core/processes/process.js @@ -0,0 +1,73 @@ +import Patterns from 'tailored'; +import States from './states'; + +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 = []; + } + + async start() { + let retval = States.NORMAL; + + try { + await this.system.set_current(this.pid); + await 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); + } + + // TODO figure out what to do with receive + async receive(clauses) { + const messages = this.mailbox.get(); + + for (let i = 0; i < messages.length; i++) { + for (const clause of clauses) { + const value = await Patterns.match_or_default_async( + 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; + } +} + +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..0c1c0a80 --- /dev/null +++ b/src/javascript/lib/core/processes/process_system.js @@ -0,0 +1,377 @@ +import ErlangTypes from 'erlang-types'; +import Mailbox from './mailbox'; +import Process from './process'; +import States from './states'; +import Scheduler from './scheduler'; + +class ProcessSystem { + constructor() { + this.pids = new Map(); + this.mailboxes = new Map(); + this.names = new Map(); + this.links = new Map(); + this.monitors = new Map(); + + this.current_process = null; + this.scheduler = new Scheduler(this); + this.suspended = new Map(); + + const process_system_scope = this; + this.main_process_pid = this.spawn(async () => { + await process_system_scope.sleep(Symbol.for('Infinity')); + }); + + this.set_current(this.main_process_pid); + } + + spawn(...args) { + if (args.length === 1) { + const fun = args[0]; + return this.add_proc(fun, [], false).pid; + } + const mod = args[0]; + const fun = args[1]; + const the_args = args[2]; + + return this.add_proc(mod[fun], the_args, false, false).pid; + } + + spawn_link(...args) { + if (args.length === 1) { + const fun = args[0]; + return this.add_proc(fun, [], true, false).pid; + } + const mod = args[0]; + const fun = args[1]; + const 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) { + const fun = args[0]; + const process = this.add_proc(fun, [], false, true); + return [process.pid, process.monitors[0]]; + } + const mod = args[0]; + const fun = args[1]; + const the_args = args[2]; + const 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; + } + 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) { + const 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) { + const newpid = new ErlangTypes.PID(); + const mailbox = new Mailbox(); + const 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 (const 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 (const 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; + } + const pid = this.whereis(id); + if (pid === null) { + throw new Error(`Process name not registered: ${id} (${typeof id})`); + } + + return pid; + } + + async send(id, msg) { + const pid = this.pidof(id); + + if (pid) { + this.mailboxes.get(pid).deliver(msg); + console.log('sending'); + console.log(pid); + if (this.suspended.has(pid)) { + console.log('Got suspended'); + const [args, resolver] = this.suspended.get(pid); + + const result = await this.__receive(pid, args); + if (result !== States.NOMATCH) { + this.suspended.delete(pid); + resolver(result); + } + } + } + + return msg; + } + + async __receive(pid, args) { + this.set_current(pid); + const process = this.pids.get(pid); + return process.receive(args); + } + + async receive(args, timeout = 0, timeoutFn = () => true) { + let DateTimeout = null; + const pid = this.current_process.pid; + console.log('Received'); + console.log(pid); + if (timeout === 0 || timeout === Infinity) { + const result = await this.__receive(pid, args); + console.log(result); + if (result !== States.NOMATCH) { + return result; + } + return this.suspend(pid, args); + } + + DateTimeout = Date.now() + timeout; + return Promise.race( + this.receive(args), + new Promise((resolver) => { + setTimeout(() => { + resolver(timeoutFn()); + }, DateTimeout); + }), + ); + } + + sleep(duration) { + this.current_process.status = States.SLEEPING; + + return new Promise((resolver) => { + if (duration !== Symbol.for('Infinity')) { + setTimeout(() => { + this.current_process.status = States.RUNNING; + resolver(true); + }, duration); + } + }); + } + + suspend(pid, args) { + this.current_process.status = States.SUSPENDED; + console.log(pid); + + return new Promise((resolver) => { + this.suspended.set(pid, [args, resolver]); + console.log('suspended'); + }); + } + + pause(pid) { + const the_pid = pid != null ? pid : this.current_process.pid; + + return new Promise((resolver) => { + this.scheduler.pause(the_pid, resolver); + }); + } + + 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 (const ref in process.monitors) { + const 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); + } + 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]; + } + return default_value; + } + + get_keys(value) { + if (value) { + const keys = []; + + for (const 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..c036546a --- /dev/null +++ b/src/javascript/lib/core/processes/scheduler.js @@ -0,0 +1,87 @@ +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(system, reductions_per_process = 8) { + this.isRunning = false; + this.invokeLater = (callback) => { + setTimeout(callback, 0); + }; + + this.system = system; + + // 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 (const [pid, queue] of this.queues) { + let reductions = 0; + while (queue && !queue.empty() && reductions < this.reductions_per_process) { + const resolver = queue.next(); + this.isRunning = true; + + resolver(true); + this.system.set_current(pid); + console.log(`Scheduler ${pid}`); + this.isRunning = false; + reductions++; + } + } + + this.invokeLater(() => { + this.run(); + }); + } + } + + pause(pid, resolver) { + this.addToQueue(pid, resolver); + } + + schedule(pid, fun, args, resolver) { + this.addToQueue(pid, [fun, args, resolver]); + } +} + +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..f024947a --- /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'), +}; diff --git a/src/javascript/lib/core/protocol.js b/src/javascript/lib/core/protocol.js index 15466ed8..b7aec13a 100644 --- a/src/javascript/lib/core/protocol.js +++ b/src/javascript/lib/core/protocol.js @@ -7,7 +7,7 @@ class Protocol { this.fallback = null; function createFun(funName) { - return function (...args) { + return async function (...args) { const thing = args[0]; let fun = null; @@ -37,7 +37,7 @@ class Protocol { } if (fun != null) { - const retval = fun.apply(this, args); + const retval = await fun.apply(this, args); return retval; } diff --git a/src/javascript/lib/core/special_forms.js b/src/javascript/lib/core/special_forms.js index 881b3cde..6ad03539 100644 --- a/src/javascript/lib/core/special_forms.js +++ b/src/javascript/lib/core/special_forms.js @@ -1,10 +1,10 @@ import Core from '../core'; -function _case(condition, clauses) { - return Core.Patterns.defmatch(...clauses)(condition); +async function _case(condition, clauses) { + return Core.Patterns.defmatchAsync(...clauses)(condition); } -function cond(...clauses) { +async function cond(...clauses) { for (const clause of clauses) { if (clause[0]) { return clause[1](); @@ -35,31 +35,34 @@ function run_list_generators(generator, generators) { return run_list_generators(next_gen, generators); } -function _for(expression, generators, collectable_protocol, into = []) { +async function _for(expression, generators, collectable_protocol, into = []) { let [result, fun] = collectable_protocol.into(into); const generatedValues = run_list_generators(generators.pop()(), generators); for (const value of generatedValues) { - if (expression.guard.apply(this, value)) { - result = fun(result, new Core.Tuple(Symbol.for('cont'), expression.fn.apply(this, value))); + if (await expression.guard.apply(this, value)) { + result = await fun( + result, + new Core.Tuple(Symbol.for('cont'), await expression.fn.apply(this, value)), + ); } } return fun(result, Symbol.for('done')); } -function _try(do_fun, rescue_function, catch_fun, else_function, after_function) { +async function _try(do_fun, rescue_function, catch_fun, else_function, after_function) { let result = null; try { - result = do_fun(); + result = await do_fun(); } catch (e) { let ex_result = null; if (rescue_function) { try { - ex_result = rescue_function(e); + ex_result = await rescue_function(e); return ex_result; } catch (ex) { if (ex instanceof Core.Patterns.MatchError) { @@ -70,7 +73,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) if (catch_fun) { try { - ex_result = catch_fun(e); + ex_result = await catch_fun(e); return ex_result; } catch (ex) { if (ex instanceof Core.Patterns.MatchError) { @@ -82,7 +85,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) throw e; } finally { if (after_function) { - after_function(); + await after_function(); } } @@ -101,7 +104,7 @@ function _try(do_fun, rescue_function, catch_fun, else_function, after_function) } } -function _with(...args) { +async function _with(...args) { let argsToPass = []; let successFunction = null; let elseFunction = null; @@ -115,9 +118,9 @@ function _with(...args) { for (let i = 0; i < args.length; i++) { const [pattern, func] = args[i]; - const result = func(...argsToPass); + const result = await func(...argsToPass); - const patternResult = Core.Patterns.match_or_default(pattern, result); + const patternResult = await Core.Patterns.match_or_default_async(pattern, result); if (patternResult == null) { if (elseFunction) { @@ -132,8 +135,8 @@ function _with(...args) { return successFunction(...argsToPass); } -function receive() { - console.warn('Receive not supported'); +async function receive(clauses, timeout = 0, timeoutFn = () => true) { + return 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 521f5b21..c743051a 100644 --- a/src/javascript/tests/core/erlang_compat/maps_spec.js +++ b/src/javascript/tests/core/erlang_compat/maps_spec.js @@ -15,8 +15,8 @@ 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 90e832cb..fe2644ef 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