hclang 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -166,15 +166,16 @@ export class EvaluationScope {
166
166
  * the argument to the argument", which is what made a closure's write target
167
167
  * and its argument the same frame and produced a self-referential binding.
168
168
  *
169
- * A handle argument is the one exception, because a handle is explicitly a
170
- * mutable view requested by the caller for exactly this purpose.
169
+ * A mutable handle argument is the one exception, because the caller asked
170
+ * for a mutable view by spelling the name with the effect marker (`x_`). A
171
+ * plain name also arrives as a handle, but an immutable one, and it grants no
172
+ * write: `f x` must not change `x`.
171
173
  */
172
174
  static call(argument, parameter = Frame.nil, closure, enclosing, receiverState) {
173
175
  const local = new Frame();
174
- const target = argument instanceof FrameHandle ? argument.unwrap() : local;
175
- const targetRole = argument instanceof FrameHandle
176
- ? "handle"
177
- : "statement";
176
+ const writable = argument instanceof FrameHandle && argument.isMutable();
177
+ const target = writable ? argument.unwrap() : local;
178
+ const targetRole = writable ? "handle" : "statement";
178
179
  const lookupFrames = [local, argument];
179
180
  if (!parameter.is.void)
180
181
  lookupFrames.push(parameter);
@@ -1,3 +1,4 @@
1
+ import { Frame } from "./frame.js";
1
2
  import { FrameExpr } from "./frame-expr.js";
2
3
  import { FrameList } from "./frame-list.js";
3
4
  import { FrameNote } from "./frame-note.js";
@@ -51,6 +52,58 @@ export class FrameArray extends FrameList {
51
52
  }
52
53
  return undefined;
53
54
  }
55
+ /** Aggregates own identity, so `===` compares them by reference. */
56
+ ownsIdentity() {
57
+ return true;
58
+ }
59
+ /**
60
+ * `=` compares the complete view, the same one `&&` iterates: visible
61
+ * properties in declaration order, then elements by index, each by `=`.
62
+ * Spelling, private fields, the declared parent, and ids are not the value.
63
+ */
64
+ equals(other) {
65
+ return this.sameMembers(other, (left, right) => left.equals(right));
66
+ }
67
+ /** `==` is the same walk with `==` at every node. */
68
+ typeEquals(other) {
69
+ return this.sameMembers(other, (left, right) => left.typeEquals(right));
70
+ }
71
+ sameMembers(other, same) {
72
+ const right = other.referent();
73
+ if (right === this)
74
+ return Frame.all;
75
+ if (right.constructor !== this.constructor)
76
+ return Frame.nil;
77
+ const inProgress = FrameArray.comparing.get(this) ?? new Set();
78
+ if (inProgress.has(right))
79
+ return Frame.all;
80
+ inProgress.add(right);
81
+ FrameArray.comparing.set(this, inProgress);
82
+ try {
83
+ const keys = this.visibleKeys();
84
+ const rightKeys = right.visibleKeys();
85
+ if (keys.length !== rightKeys.length ||
86
+ keys.some((key, index) => key !== rightKeys[index]))
87
+ return Frame.nil;
88
+ for (const key of keys) {
89
+ if (same(this.get_here(key), right.get_here(key)) !== Frame.all) {
90
+ return Frame.nil;
91
+ }
92
+ }
93
+ const elements = this.elements();
94
+ const rightElements = right.elements();
95
+ if (elements.length !== rightElements.length)
96
+ return Frame.nil;
97
+ return elements.every((element, index) => same(element, rightElements[index]) === Frame.all)
98
+ ? Frame.all
99
+ : Frame.nil;
100
+ }
101
+ finally {
102
+ inProgress.delete(right);
103
+ if (inProgress.size === 0)
104
+ FrameArray.comparing.delete(this);
105
+ }
106
+ }
54
107
  /**
55
108
  * Aggregates are where identity matters, so an instance copy isolates them:
56
109
  * every nested aggregate, in the data plane and the metadata plane alike, gets
@@ -127,3 +180,10 @@ Object.defineProperty(FrameArray, "SIGIL_STARTS", {
127
180
  { key: FrameArray.END_ARRAY, mode: "pop" },
128
181
  ]
129
182
  });
183
+ /** Pairs under comparison, so a frame reachable from itself terminates. */
184
+ Object.defineProperty(FrameArray, "comparing", {
185
+ enumerable: true,
186
+ configurable: true,
187
+ writable: true,
188
+ value: new Map()
189
+ });
@@ -60,6 +60,20 @@ export class FrameHandle extends Frame {
60
60
  unwrap() {
61
61
  return this.target;
62
62
  }
63
+ /** A handle is transparent to equality: it compares as its target. */
64
+ referent() {
65
+ return this.target.referent();
66
+ }
67
+ equals(other) {
68
+ return this.target.equals(other);
69
+ }
70
+ typeEquals(other) {
71
+ return this.target.typeEquals(other);
72
+ }
73
+ /** Whether the name that produced this handle carried the effect marker. */
74
+ isMutable() {
75
+ return this.mutable;
76
+ }
63
77
  /** The lexical fallback selected for this particular read. */
64
78
  readContextFrame() {
65
79
  return this.readContext;
@@ -66,6 +66,13 @@ export class FrameTypedNumber extends FrameNumeric {
66
66
  }
67
67
  return super.lookup_here(key, origin);
68
68
  }
69
+ /** `==` also asks the magnitude's rung: `3.m == 3.0.m` is nil. */
70
+ typeEquals(right) {
71
+ if (!(right instanceof FrameTypedNumber) || this.unit !== right.unit) {
72
+ return Frame.nil;
73
+ }
74
+ return this.magnitude.typeEquals(right.magnitude);
75
+ }
69
76
  equals(right) {
70
77
  if (!(right instanceof FrameTypedNumber) || this.unit !== right.unit) {
71
78
  return Frame.nil;
@@ -179,6 +179,41 @@ export class Frame extends MetaFrame {
179
179
  equals(other) {
180
180
  return this.toString() === other.toString() ? Frame.all : Frame.nil;
181
181
  }
182
+ /**
183
+ * typeEquals is `==`: the same value, and the same type at every node.
184
+ * It is strictly stronger than `equals`, so `3 == 3.0` is nil while
185
+ * `3 = 3.0` holds.
186
+ */
187
+ typeEquals(other) {
188
+ const left = this.referent();
189
+ const right = other.referent();
190
+ return left.constructor === right.constructor
191
+ ? left.equals(right)
192
+ : Frame.nil;
193
+ }
194
+ /**
195
+ * identical is `===`: whether both references reach the same frame.
196
+ *
197
+ * Only a frame that owns identity can be told apart from an equal copy.
198
+ * Atoms and closure bodies are shared by the copy contract, so sharing them
199
+ * is unobservable and identity falls back to `typeEquals`, which keeps the
200
+ * ladder monotone: `===` implies `==` implies `=`. No id is ever exposed.
201
+ */
202
+ identical(other) {
203
+ const left = this.referent();
204
+ const right = other.referent();
205
+ if (left.ownsIdentity())
206
+ return left === right ? Frame.all : Frame.nil;
207
+ return left.typeEquals(right);
208
+ }
209
+ /** The frame a reference reaches. A handle answers its target. */
210
+ referent() {
211
+ return this;
212
+ }
213
+ /** Whether a write can land on this frame, so that identity is observable. */
214
+ ownsIdentity() {
215
+ return false;
216
+ }
182
217
  /** Compare only this frame's data plane, excluding metadata. */
183
218
  dataEquals(other) {
184
219
  return this.dataString() === other.dataString() ? Frame.all : Frame.nil;
@@ -5,9 +5,14 @@ export const IfThen = (source, block, receiverState) => {
5
5
  }
6
6
  return Frame.nil;
7
7
  };
8
+ /**
9
+ * `:` is "or else": a value that is not nil answers itself, and only nil calls
10
+ * the fallback. Composed after `?`, that makes `c ? t : e` answer `t` or `e`,
11
+ * because the taken branch's value reaches `:` as its source.
12
+ */
8
13
  export const IfElse = (source, block, receiverState) => {
9
14
  if (source === Frame.nil) {
10
15
  return block.call(Frame.nil, Frame.nil, receiverState);
11
16
  }
12
- return Frame.nil;
17
+ return source;
13
18
  };
@@ -13,11 +13,13 @@ export const Power = (source, block) => numeric(source, block, (left, right) =>
13
13
  export const Equals = (source, block) => {
14
14
  return source.equals(block);
15
15
  };
16
- export const DataEquals = (source, block) => {
17
- return source.dataEquals(block);
16
+ /** `==`: the same value and the same type at every node. */
17
+ export const TypeEquals = (source, block) => {
18
+ return source.typeEquals(block);
18
19
  };
19
- export const MetadataEquals = (source, block) => {
20
- return source.metadataEquals(block);
20
+ /** `===`: the same frame. Atoms, which own no identity, fall back to `==`. */
21
+ export const Identical = (source, block) => {
22
+ return source.identical(block);
21
23
  };
22
24
  export const GreaterThan = (source, block) => numeric(source, block, (left, right) => left.greaterThan(right));
23
25
  export const GreaterThanOrEqual = (source, block) => numeric(source, block, (left, right) => left.greaterThanOrEqual(right));
package/esm/lib/ops.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { IfElse, IfThen } from "./ops/conditionals.js";
2
2
  import { FrameOps } from "./ops/frame-ops.js";
3
3
  import { MapElements, MapMembers, ReduceElements, ReduceMembers, } from "./ops/iterators.js";
4
- import { Add, DataEquals, Divide, Equals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, MetadataEquals, Modulo, Multiply, Power, Subtract, } from "./ops/math.js";
4
+ import { Add, Divide, Equals, GreaterThan, GreaterThanOrEqual, Identical, LessThan, LessThanOrEqual, Modulo, Multiply, Power, Subtract, TypeEquals, } from "./ops/math.js";
5
5
  import { BindType, HasType } from "./ops/type-operations.js";
6
6
  export { FrameCurry } from "./ops/frame-curry.js";
7
7
  export const Ops = new FrameOps({
@@ -18,8 +18,8 @@ export const Ops = new FrameOps({
18
18
  "%%": Modulo,
19
19
  "**": Power,
20
20
  "=": Equals,
21
- "==": DataEquals,
22
- "===": MetadataEquals,
21
+ "==": TypeEquals,
22
+ "===": Identical,
23
23
  ">": GreaterThan,
24
24
  ">>": GreaterThan,
25
25
  ">=": GreaterThanOrEqual,
@@ -1 +1 @@
1
- export const VERSION = "0.16.0";
1
+ export const VERSION = "0.17.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hclang",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Homoiconic C command-line interpreter and REPL",
5
5
  "keywords": [
6
6
  "language",