mirror of
https://github.com/voltrevo/ValueScript.git
synced 2026-01-10 14:08:09 -05:00
Add structural comparison
This commit is contained in:
@@ -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]`
|
||||
@@ -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
|
||||
|
||||
39
inputs/passing/structureCmp.ts
Normal file
39
inputs/passing/structureCmp.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem::take;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
@@ -124,8 +125,64 @@ 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 ({}, {})",
|
||||
@@ -133,11 +190,34 @@ pub fn op_eq_impl(left: &Val, right: &Val) -> Result<bool, Val> {
|
||||
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)?))
|
||||
}
|
||||
@@ -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,
|
||||
_ => {
|
||||
if left.typeof_() != right.typeof_() {
|
||||
false
|
||||
} else {
|
||||
return Err("TODO: op=== with other types".to_internal_error());
|
||||
(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_() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
return Err(
|
||||
format!("TODO: op=== with special types ({}, {})", left, right).to_internal_error(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
assert!(left.typeof_() != right.typeof_());
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user