From 848a660c16a786fc895953a28ff0b95ed3af8e7a Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 9 Sep 2017 03:36:20 -0500 Subject: [PATCH 1/3] Add equals function to better calculate equality --- lib/elixir_script/passes/translate/form.ex | 14 ++- src/javascript/lib/core.js | 1 + .../lib/core/erlang_compat/erlang.js | 98 +++++++++++++++++++ .../tests/core/erlang_compat/erlang_spec.js | 34 +++++++ src/javascript/tests/core/functions.spec.js | 29 ++++-- test/integration/integration_test.exs | 5 + test/support/integration.ex | 6 ++ 7 files changed, 176 insertions(+), 11 deletions(-) diff --git a/lib/elixir_script/passes/translate/form.ex b/lib/elixir_script/passes/translate/form.ex index 89fef5d5..6a1de398 100644 --- a/lib/elixir_script/passes/translate/form.ex +++ b/lib/elixir_script/passes/translate/form.ex @@ -253,7 +253,19 @@ defmodule ElixirScript.Translate.Form do {ast, state} end - def compile({{:., _, [:erlang, op]}, _, [left, right]}, state) when op in [:+, :-, :*, :/, :==, :>, :<, :>=] do + def compile({{:., _, [:erlang, op]}, _, [left, right]}, state) when op in [:==, :===] do + ast = Helpers.call( + J.member_expression( + Helpers.core_module("erlang"), + J.identifier("equals") + ), + [compile!(left, state), compile!(right, state)] + ) + + {ast, state} + end + + def compile({{:., _, [:erlang, op]}, _, [left, right]}, state) when op in [:+, :-, :*, :/, :>, :<, :>=] do ast = J.binary_expression( op, compile!(left, state), diff --git a/src/javascript/lib/core.js b/src/javascript/lib/core.js index 0d9c7342..2b3e9489 100644 --- a/src/javascript/lib/core.js +++ b/src/javascript/lib/core.js @@ -39,6 +39,7 @@ export default { Tuple: ErlangTypes.Tuple, PID: ErlangTypes.PID, BitString: ErlangTypes.BitString, + Reference: ErlangTypes.Reference, Patterns, Integer, Float, diff --git a/src/javascript/lib/core/erlang_compat/erlang.js b/src/javascript/lib/core/erlang_compat/erlang.js index 27f81060..b3682a7c 100644 --- a/src/javascript/lib/core/erlang_compat/erlang.js +++ b/src/javascript/lib/core/erlang_compat/erlang.js @@ -66,6 +66,103 @@ function list_subtraction(list1, list2) { return list; } +function arrayEquals(left, right) { + if (!Array.isArray(right)) { + return false; + } + + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + if (equals(left[i], right[i]) === false) { + return false; + } + } + + return true; +} + +function tupleEquals(left, right) { + if (right instanceof ErlangTypes.Tuple === false) { + return false; + } + + if (left.length !== right.length) { + return false; + } + + return arrayEquals(left.values, right.values); +} + +function bitstringEquals(left, right) { + if (right instanceof ErlangTypes.BitString === false) { + return false; + } + + if (left.length !== right.length) { + return false; + } + + return arrayEquals(left.value, right.value); +} + +function pidEquals(left, right) { + if (right instanceof ErlangTypes.PID === false) { + return false; + } + + return left.id === right.id; +} + +function referenceEquals(left, right) { + if (right instanceof ErlangTypes.Reference === false) { + return false; + } + + return left.id === right.id; +} + +function mapEquals(left, right) { + if (right instanceof Map === false) { + return false; + } + + const leftEntries = Array.from(left.entries()); + const rightEntries = Array.from(right.entries()); + + return arrayEquals(leftEntries, rightEntries); +} + +function equals(left, right) { + if (Array.isArray(left)) { + return arrayEquals(left, right); + } + + if (left instanceof ErlangTypes.Tuple) { + return tupleEquals(left, right); + } + + if (left instanceof ErlangTypes.PID) { + return pidEquals(left, right); + } + + if (left instanceof ErlangTypes.BitString) { + return bitstringEquals(left, right); + } + + if (left instanceof ErlangTypes.Reference) { + return referenceEquals(left, right); + } + + if (left instanceof Map) { + return mapEquals(left, right); + } + + return left === right; +} + function div(left, right) { return left / right; } @@ -473,4 +570,5 @@ export default { list_to_binary, nodes, function_exported, + equals, }; diff --git a/src/javascript/tests/core/erlang_compat/erlang_spec.js b/src/javascript/tests/core/erlang_compat/erlang_spec.js index 6c9e5056..65f757fd 100644 --- a/src/javascript/tests/core/erlang_compat/erlang_spec.js +++ b/src/javascript/tests/core/erlang_compat/erlang_spec.js @@ -98,3 +98,37 @@ test('nodes/1', (t) => { t.deepEqual(Core.erlang.nodes([Symbol.for('connected')]), []); }); + +test('equals', (t) => { + t.is(Core.erlang.equals(1, 1), true); + t.is(Core.erlang.equals(1, 'a'), false); + t.is(Core.erlang.equals('a', 'a'), true); + t.is(Core.erlang.equals('a', 'b'), false); + t.is(Core.erlang.equals(Symbol.for('this'), Symbol.for('this')), true); + t.is(Core.erlang.equals([], []), true); + t.is(Core.erlang.equals([1], []), false); + t.is( + Core.erlang.equals( + new Map([[Symbol.for('nest1'), 'valuenest1']]), + new Map([[Symbol.for('nest2'), 'valuenest2']]), + ), + false, + ); + t.is( + Core.erlang.equals( + new Map([[Symbol.for('nest1'), 'valuenest1']]), + new Map([[Symbol.for('nest1'), 'valuenest1']]), + ), + true, + ); + t.is(Core.erlang.equals(new Core.Tuple('abc'), new Core.Tuple('abc')), true); + t.is(Core.erlang.equals(new Core.Tuple('abc'), new Core.Tuple('abc', 's')), false); + + const pid = new Core.PID(); + t.is(Core.erlang.equals(pid, pid), true); + t.is(Core.erlang.equals(pid, new Core.PID()), false); + + const ref = new Core.Reference(); + t.is(Core.erlang.equals(ref, ref), true); + t.is(Core.erlang.equals(ref, new Core.Reference()), false); +}); diff --git a/src/javascript/tests/core/functions.spec.js b/src/javascript/tests/core/functions.spec.js index 5a114c6f..dfb15ba1 100644 --- a/src/javascript/tests/core/functions.spec.js +++ b/src/javascript/tests/core/functions.spec.js @@ -45,7 +45,7 @@ test('object_to_map/1', (t) => { let result = Functions.object_to_map(obj); t.deepEqual(result, new Map()); - obj = {key: 'value'}; + obj = { key: 'value' }; result = Functions.object_to_map(obj); t.deepEqual(result, new Map([['key', 'value']])); @@ -60,17 +60,26 @@ test('object_to_map/2', (t) => { let result = Functions.object_to_map(obj, []); t.deepEqual(result, new Map()); - obj = {key: 'value'}; + obj = { key: 'value' }; result = Functions.object_to_map(obj, [new Core.Tuple(Symbol.for('keys'), Symbol.for('atom'))]); t.deepEqual(result, new Map([[Symbol.for('key'), 'value']])); obj = {}; - obj[Symbol.for('key')] = [{nest1: 'valuenest1'},{nest2: 'valuenest2'}]; + obj[Symbol.for('key')] = [{ nest1: 'valuenest1' }, { nest2: 'valuenest2' }]; result = Functions.object_to_map(obj, [ - new Core.Tuple(Symbol.for('keys'), Symbol.for('atom')), - new Core.Tuple(Symbol.for('recurse_array'), true)]); - t.deepEqual(result, new Map([[Symbol.for('key'), [ - new Map([[Symbol.for('nest1'), 'valuenest1']]), - new Map([[Symbol.for('nest2'), 'valuenest2']]) - ]]])); -}); \ No newline at end of file + new Core.Tuple(Symbol.for('keys'), Symbol.for('atom')), + new Core.Tuple(Symbol.for('recurse_array'), true), + ]); + t.deepEqual( + result, + new Map([ + [ + Symbol.for('key'), + [ + new Map([[Symbol.for('nest1'), 'valuenest1']]), + new Map([[Symbol.for('nest2'), 'valuenest2']]), + ], + ], + ]), + ); +}); diff --git a/test/integration/integration_test.exs b/test/integration/integration_test.exs index 434018f6..d64d8da5 100644 --- a/test/integration/integration_test.exs +++ b/test/integration/integration_test.exs @@ -19,4 +19,9 @@ defmodule ElixirScript.Integration.Test do [:option, %{value: "test2@hotmail.com"}, "test2@hotmail.com"] ] end + + test "map equals" do + val = call_compiled_function Integration, :map_equals, [] + assert val == true + end end diff --git a/test/support/integration.ex b/test/support/integration.ex index abac2760..f5b29e1a 100644 --- a/test/support/integration.ex +++ b/test/support/integration.ex @@ -10,4 +10,10 @@ defmodule Integration do options = Enum.reduce(orders, [], &(&2 ++ [ [:option, %{value: &1.email}, &1.email] ])) end + + def map_equals do + map1 = %{test: "map"} + map2 = %{test: "map"} + map1 == map2 + end end From 9733a056d88bace491ba77c9c41edbd312dbdd81 Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 9 Sep 2017 03:44:54 -0500 Subject: [PATCH 2/3] Fixing lints --- .eslintrc.js | 1 + .../lib/core/erlang_compat/binary.js | 27 +++++++++---------- .../tests/core/erlang_compat/binary_spec.js | 4 ++- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9112ed27..07f0a62d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { 'no-restricted-syntax': 'off', 'no-underscore-dangle': 'off', 'import/extensions': 'off', + 'import/no-extraneous-dependencies': ['error', { devDependencies: false }], }, extends: 'airbnb-base', plugins: ['import'], diff --git a/src/javascript/lib/core/erlang_compat/binary.js b/src/javascript/lib/core/erlang_compat/binary.js index 70963173..20a7b629 100644 --- a/src/javascript/lib/core/erlang_compat/binary.js +++ b/src/javascript/lib/core/erlang_compat/binary.js @@ -10,15 +10,15 @@ function copy(subject, n = 1) { } function first(subject) { - if (subject.length == 0) { - throw new Error(`Binary is of length 0`); + if (subject.length === 0) { + throw new Error('Binary is of length 0'); } return at(subject, 0); } function last(subject) { - if (subject.length == 0) { - throw new Error(`Binary is of length 0`); + if (subject.length === 0) { + throw new Error('Binary is of length 0'); } return subject.slice(-1); } @@ -29,20 +29,19 @@ function list_to_bin(bytelist) { function part(subject, posOrTuple, len = null) { if (len === null) { - var pos; - [pos, len] = posOrTuple.values; - return subject.substr(pos, len); - } else { - return subject.substr(posOrTuple, len); + const [pos, theLen] = posOrTuple.values; + return subject.substr(pos, theLen); } + + return subject.substr(posOrTuple, len); } -//TODO: Support more options -//TODO: pattern cannot be list of strings +// TODO: Support more options +// TODO: pattern cannot be list of strings function replace(subject, pattern, replacement, options = []) { const opt_global = proplists.get_value(Symbol.for('global'), options); - var regex; + let regex; if (opt_global !== Symbol.for('undefined')) { regex = new RegExp(pattern, 'g'); } else { @@ -52,8 +51,8 @@ function replace(subject, pattern, replacement, options = []) { return subject.replace(regex, replacement); } -//TODO: Support more options, global is implied -//TODO: pattern cannot be list of strings +// TODO: Support more options, global is implied +// TODO: pattern cannot be list of strings function split(subject, pattern, options = []) { return subject.split(pattern); } diff --git a/src/javascript/tests/core/erlang_compat/binary_spec.js b/src/javascript/tests/core/erlang_compat/binary_spec.js index cc56e1dd..2bef01af 100644 --- a/src/javascript/tests/core/erlang_compat/binary_spec.js +++ b/src/javascript/tests/core/erlang_compat/binary_spec.js @@ -55,7 +55,9 @@ test('replace/3', (t) => { }); test('replace/4', (t) => { - const result = Core.binary.replace('abcb', 'b', 'c', [new Core.Tuple(Symbol.for('global'), true)]); + const result = Core.binary.replace('abcb', 'b', 'c', [ + new Core.Tuple(Symbol.for('global'), true), + ]); t.deepEqual(result, 'accc'); }); From 646ef18000cabee2af2f3f3fb41e7cdda0c53d7c Mon Sep 17 00:00:00 2001 From: Bryan Joseph Date: Sat, 9 Sep 2017 03:48:15 -0500 Subject: [PATCH 3/3] Allow devDeps in eslint rules --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 07f0a62d..3b27e64a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = { 'no-restricted-syntax': 'off', 'no-underscore-dangle': 'off', 'import/extensions': 'off', - 'import/no-extraneous-dependencies': ['error', { devDependencies: false }], + 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], }, extends: 'airbnb-base', plugins: ['import'],