Skip to content

Commit

Permalink
Add structural comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
voltrevo committed Jun 29, 2023
1 parent b2bb618 commit 05efdfd
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 9 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ not the subset of ValueScript that has actually been implemented.
- Iterators
- Spread operator on iterables
- Generators
- Structural comparison (not yet for class instances)
- `{} === {} -> true`
- JS: `-> false`
- This is a value semantics thing - objects don't have identity
- Many unusual JS things:
- `[] + [] -> ""`
- `[10, 1, 3].sort() -> [1, 10, 3]`
Expand Down Expand Up @@ -468,10 +472,7 @@ not the subset of ValueScript that has actually been implemented.
- Uses `.toString()` to get the source code and compiles and runs it in
WebAssembly
- C libraries, and bindings for python etc
- Structural comparison
- `{} === {} -> true`
- JS: `-> false`
- This is a value semantics thing - objects don't have identity
- Comparison of class instances and functions
- Object spreading
- Rest params
- Async functions
Expand Down
39 changes: 39 additions & 0 deletions inputs/passing/structureCmp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! test_output(24)

export default function () {
let count = 0;

const cases: [unknown, unknown, { loose: boolean; strict: boolean }][] = [
[[], [], { loose: true, strict: true }],
[[], [1], { loose: false, strict: false }],
[[1, 2, 3], [1, 2, 3], { loose: true, strict: true }],
[[1, 2, 3], [1, "2", 3], { loose: true, strict: false }],
[{}, {}, { loose: true, strict: true }],
[{}, { x: 1 }, { loose: false, strict: false }],
[{}, { [Symbol.iterator]: 1 }, { loose: false, strict: false }],
[{ x: 1, y: 2, z: 3 }, { x: 1, y: 2, z: 3 }, { loose: true, strict: true }],
[{ x: 1, y: 2, z: 3 }, { x: 1, y: "2", z: 3 }, {
loose: true,
strict: false,
}],
[[[[[[1]]]]], [[[[[1]]]]], { loose: true, strict: true }],
[[[[[["1"]]]]], [[[[[1]]]]], { loose: true, strict: false }],
[null, undefined, { loose: true, strict: false }],
];

for (const [left, right, { loose, strict }] of cases) {
if ((left == right) === loose) {
count++;
} else {
throw new Error(`Expected ${left} == ${right} to be ${loose}`);
}

if ((left === right) === strict) {
count++;
} else {
throw new Error(`Expected ${left} === ${right} to be ${strict}`);
}
}

return count;
}
143 changes: 138 additions & 5 deletions valuescript_vm/src/operations.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::mem::take;
use std::rc::Rc;
use std::str::FromStr;
Expand Down Expand Up @@ -124,20 +125,99 @@ pub fn op_eq_impl(left: &Val, right: &Val) -> Result<bool, Val> {
(Val::Bool(left_bool), Val::Bool(right_bool)) => left_bool == right_bool,
(Val::Number(left_number), Val::Number(right_number)) => left_number == right_number,
(Val::String(left_string), Val::String(right_string)) => left_string == right_string,
(Val::Number(left_number), Val::String(right_string)) => {
left_number.to_string() == **right_string
}
(Val::String(left_string), Val::Number(right_number)) => {
**left_string == right_number.to_string()
}
(Val::BigInt(left_bigint), Val::BigInt(right_bigint)) => left_bigint == right_bigint,
(Val::Array(left_array), Val::Array(right_array)) => 'b: {
if &**left_array as *const _ == &**right_array as *const _ {
break 'b true;
}

let len = left_array.elements.len();

if right_array.elements.len() != len {
break 'b false;
}

for (left_item, right_item) in left_array.elements.iter().zip(right_array.elements.iter()) {
if !op_eq_impl(left_item, right_item)? {
break 'b false;
}
}

true
}
(Val::Object(left_object), Val::Object(right_object)) => 'b: {
if left_object.prototype.is_some() || right_object.prototype.is_some() {
return Err("TODO: class instance comparison".to_internal_error());
}

if &**left_object as *const _ == &**right_object as *const _ {
break 'b true;
}

if !compare_btrees(
&left_object.string_map,
&right_object.string_map,
op_eq_impl,
)? {
break 'b false;
}

if !compare_btrees(
&left_object.symbol_map,
&right_object.symbol_map,
op_eq_impl,
)? {
break 'b false;
}

true
}
_ => {
if left.is_truthy() != right.is_truthy() {
return Ok(false);
}

return Err(
format!(
"TODO: op== with other types ({}, {})",
left.codify(),
right.codify()
)
.to_internal_error(),
)
);
}
})
}

fn compare_btrees<K, Cmp>(
left: &BTreeMap<K, Val>,
right: &BTreeMap<K, Val>,
cmp: Cmp,
) -> Result<bool, Val>
where
Cmp: Fn(&Val, &Val) -> Result<bool, Val>,
{
let symbol_len = left.len();

if right.len() != symbol_len {
return Ok(false);
}

for (left_value, right_value) in left.values().zip(right.values()) {
if !cmp(left_value, right_value)? {
return Ok(false);
}
}

Ok(true)
}

pub fn op_eq(left: &Val, right: &Val) -> Result<Val, Val> {
Ok(Val::Bool(op_eq_impl(left, right)?))
}
Expand All @@ -154,12 +234,65 @@ pub fn op_triple_eq_impl(left: &Val, right: &Val) -> Result<bool, Val> {
(Val::Number(left_number), Val::Number(right_number)) => left_number == right_number,
(Val::String(left_string), Val::String(right_string)) => left_string == right_string,
(Val::BigInt(left_bigint), Val::BigInt(right_bigint)) => left_bigint == right_bigint,
_ => {
(Val::Array(left_array), Val::Array(right_array)) => 'b: {
if &**left_array as *const _ == &**right_array as *const _ {
break 'b true;
}

let len = left_array.elements.len();

if right_array.elements.len() != len {
break 'b false;
}

for (left_item, right_item) in left_array.elements.iter().zip(right_array.elements.iter()) {
if !op_triple_eq_impl(left_item, right_item)? {
break 'b false;
}
}

true
}
(Val::Object(left_object), Val::Object(right_object)) => 'b: {
if left_object.prototype.is_some() || right_object.prototype.is_some() {
return Err("TODO: class instance comparison".to_internal_error());
}

if &**left_object as *const _ == &**right_object as *const _ {
break 'b true;
}

if !compare_btrees(
&left_object.string_map,
&right_object.string_map,
op_triple_eq_impl,
)? {
break 'b false;
}

if !compare_btrees(
&left_object.symbol_map,
&right_object.symbol_map,
op_triple_eq_impl,
)? {
break 'b false;
}

true
}
(Val::Static(..) | Val::Dynamic(..) | Val::CopyCounter(..), _)
| (_, Val::Static(..) | Val::Dynamic(..) | Val::CopyCounter(..)) => {
if left.typeof_() != right.typeof_() {
false
} else {
return Err("TODO: op=== with other types".to_internal_error());
return Ok(false);
}

return Err(
format!("TODO: op=== with special types ({}, {})", left, right).to_internal_error(),
);
}
_ => {
assert!(left.typeof_() != right.typeof_());
false
}
})
}
Expand Down
1 change: 1 addition & 0 deletions website/src/playground/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const orderedFiles = [
"/tutorial/revertOnCatch.ts",
"/tutorial/strings.ts",
"/tutorial/const.ts",
"/tutorial/structuralComparison.ts",
"/tutorial/binaryTree.ts",
"/tutorial/specialFunctions.ts",
"/tutorial/treeShaking.ts",
Expand Down
22 changes: 22 additions & 0 deletions website/src/playground/files/root/tutorial/structuralComparison.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Also due to value semantics, arrays and objects are compared on their content instead of their
// identity.

export default function () {
return vec3(-5, 7, 12) === vec3(-5, 7, 12);
// JavaScript: false
// ValueScript: true
}

function vec3(x: number, y: number, z: number) {
return { x, y, z };
}

// Caveat:
// - TypeScript will emit an error for expressions like `[a, b] === [c, d]`.
//
// This is unfortunate. It's to protect you from accidentally expecting structural comparison in JS,
// but in ValueScript, that's exactly how it *does* work.
//
// ValueScript will still happily evaluate these expressions (ValueScript doesn't use the TS
// compiler), but you might want to rewrite these expressions as `eq([a, b], [c, d])` until
// dedicated ValueScript intellisense is available.

0 comments on commit 05efdfd

Please sign in to comment.