conjure-js 0.0.12 → 0.0.13
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.
- package/dist-cli/conjure-js.mjs +9360 -5298
- package/dist-vite-plugin/index.mjs +9463 -5185
- package/package.json +3 -1
- package/src/bin/cli.ts +2 -2
- package/src/bin/nrepl-symbol.ts +150 -0
- package/src/bin/nrepl.ts +289 -167
- package/src/bin/version.ts +1 -1
- package/src/clojure/core.clj +757 -29
- package/src/clojure/core.clj.d.ts +75 -131
- package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
- package/src/clojure/generated/clojure-core-source.ts +758 -29
- package/src/clojure/generated/clojure-set-source.ts +136 -0
- package/src/clojure/generated/clojure-walk-source.ts +72 -0
- package/src/clojure/set.clj +132 -0
- package/src/clojure/set.clj.d.ts +20 -0
- package/src/clojure/string.clj.d.ts +14 -0
- package/src/clojure/walk.clj +68 -0
- package/src/clojure/walk.clj.d.ts +7 -0
- package/src/core/assertions.ts +114 -6
- package/src/core/bootstrap.ts +337 -0
- package/src/core/conversions.ts +48 -31
- package/src/core/core-module.ts +303 -0
- package/src/core/env.ts +20 -6
- package/src/core/evaluator/apply.ts +40 -25
- package/src/core/evaluator/arity.ts +8 -8
- package/src/core/evaluator/async-evaluator.ts +565 -0
- package/src/core/evaluator/collections.ts +28 -5
- package/src/core/evaluator/destructure.ts +180 -69
- package/src/core/evaluator/dispatch.ts +12 -14
- package/src/core/evaluator/evaluate.ts +22 -20
- package/src/core/evaluator/expand.ts +45 -15
- package/src/core/evaluator/form-parsers.ts +178 -0
- package/src/core/evaluator/index.ts +7 -9
- package/src/core/evaluator/js-interop.ts +189 -0
- package/src/core/evaluator/quasiquote.ts +14 -8
- package/src/core/evaluator/recur-check.ts +6 -6
- package/src/core/evaluator/special-forms.ts +234 -191
- package/src/core/factories.ts +182 -3
- package/src/core/index.ts +54 -4
- package/src/core/module.ts +136 -0
- package/src/core/ns-forms.ts +107 -0
- package/src/core/printer.ts +371 -11
- package/src/core/reader.ts +84 -33
- package/src/core/registry.ts +209 -0
- package/src/core/runtime.ts +376 -0
- package/src/core/session.ts +253 -487
- package/src/core/stdlib/arithmetic.ts +528 -194
- package/src/core/stdlib/async-fns.ts +132 -0
- package/src/core/stdlib/atoms.ts +291 -56
- package/src/core/stdlib/errors.ts +54 -50
- package/src/core/stdlib/hof.ts +82 -166
- package/src/core/stdlib/js-namespace.ts +344 -0
- package/src/core/stdlib/lazy.ts +34 -0
- package/src/core/stdlib/maps-sets.ts +322 -0
- package/src/core/stdlib/meta.ts +61 -30
- package/src/core/stdlib/predicates.ts +325 -187
- package/src/core/stdlib/regex.ts +126 -98
- package/src/core/stdlib/seq.ts +564 -0
- package/src/core/stdlib/strings.ts +164 -135
- package/src/core/stdlib/transducers.ts +95 -100
- package/src/core/stdlib/utils.ts +292 -130
- package/src/core/stdlib/vars.ts +27 -27
- package/src/core/stdlib/vectors.ts +122 -0
- package/src/core/tokenizer.ts +2 -2
- package/src/core/transformations.ts +117 -9
- package/src/core/types.ts +98 -2
- package/src/host/node-host-module.ts +74 -0
- package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
- package/src/vite-plugin-clj/codegen.ts +87 -95
- package/src/vite-plugin-clj/index.ts +178 -23
- package/src/vite-plugin-clj/namespace-utils.ts +39 -0
- package/src/vite-plugin-clj/static-analysis.ts +211 -0
- package/src/clojure/demo.clj +0 -72
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/core/core-env.ts +0 -61
- package/src/core/stdlib/collections.ts +0 -739
- package/src/host/node.ts +0 -55
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async stdlib functions: then, catch*, pending?, promise-of
|
|
3
|
+
* EXPERIMENTAL — part of CljPending support. Deleteable.
|
|
4
|
+
* To revert: delete this file and remove the import from core-module.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { v } from '../factories'
|
|
8
|
+
import { CljThrownSignal, EvaluationError } from '../errors'
|
|
9
|
+
import { is } from '../assertions'
|
|
10
|
+
import { printString } from '../printer'
|
|
11
|
+
import { toSeq } from '../transformations'
|
|
12
|
+
import type { EvaluationContext, CljValue, Env } from '../types'
|
|
13
|
+
|
|
14
|
+
export const asyncFunctions: Record<string, CljValue> = {
|
|
15
|
+
// (then val f) — apply f when resolved, or immediately if val is not pending
|
|
16
|
+
then: v
|
|
17
|
+
.nativeFnCtx(
|
|
18
|
+
'then',
|
|
19
|
+
(ctx: EvaluationContext, callEnv: Env, val: CljValue, f: CljValue) => {
|
|
20
|
+
if (!is.callable(f)) {
|
|
21
|
+
throw new EvaluationError(
|
|
22
|
+
`${printString(f)} is not a callable value`,
|
|
23
|
+
{ fn: f, args: [] }
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
if (val.kind !== 'pending') {
|
|
27
|
+
return ctx.applyCallable(f, [val], callEnv)
|
|
28
|
+
}
|
|
29
|
+
const promise = val.promise.then((resolved) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = ctx.applyCallable(f, [resolved], callEnv)
|
|
32
|
+
// Unwrap nested CljPending for transparent chaining
|
|
33
|
+
return result.kind === 'pending' ? result.promise : result
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return Promise.reject(e)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return v.pending(promise)
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
.doc(
|
|
42
|
+
'Applies f to the resolved value of a pending, or to val directly if not pending.',
|
|
43
|
+
[['val', 'f']]
|
|
44
|
+
),
|
|
45
|
+
|
|
46
|
+
// (catch* val f) — handle rejection; named catch* to avoid collision with catch special form
|
|
47
|
+
'catch*': v
|
|
48
|
+
.nativeFnCtx(
|
|
49
|
+
'catch*',
|
|
50
|
+
(ctx: EvaluationContext, callEnv: Env, val: CljValue, f: CljValue) => {
|
|
51
|
+
if (!is.callable(f)) {
|
|
52
|
+
throw new EvaluationError(
|
|
53
|
+
`${printString(f)} is not a callable value`,
|
|
54
|
+
{ fn: f, args: [] }
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
if (val.kind !== 'pending') return val // not pending — no rejection possible
|
|
58
|
+
const promise = val.promise.catch((err) => {
|
|
59
|
+
// Normalize the thrown value to a CljValue map
|
|
60
|
+
let errVal: CljValue
|
|
61
|
+
if (err instanceof CljThrownSignal) {
|
|
62
|
+
// (throw ...) inside async: pass the thrown value directly
|
|
63
|
+
errVal = err.value
|
|
64
|
+
} else {
|
|
65
|
+
errVal = {
|
|
66
|
+
kind: 'map',
|
|
67
|
+
entries: [
|
|
68
|
+
[
|
|
69
|
+
{ kind: 'keyword', name: ':type' },
|
|
70
|
+
{ kind: 'keyword', name: ':error/js' },
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
{ kind: 'keyword', name: ':message' },
|
|
74
|
+
{
|
|
75
|
+
kind: 'string',
|
|
76
|
+
value: err instanceof Error ? err.message : String(err),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const result = ctx.applyCallable(f, [errVal], callEnv)
|
|
84
|
+
return result.kind === 'pending' ? result.promise : result
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return Promise.reject(e)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
return v.pending(promise)
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
.doc(
|
|
93
|
+
'Handles rejection of a pending value by calling f with the thrown value or an error map.',
|
|
94
|
+
[['val', 'f']]
|
|
95
|
+
),
|
|
96
|
+
|
|
97
|
+
// (pending? x) → boolean
|
|
98
|
+
'pending?': v
|
|
99
|
+
.nativeFn('pending?', (val: CljValue) => {
|
|
100
|
+
return v.boolean(val.kind === 'pending')
|
|
101
|
+
})
|
|
102
|
+
.doc('Returns true if val is a pending (async) value.', [['val']]),
|
|
103
|
+
|
|
104
|
+
// (promise-of val) → CljPending that resolves immediately with val
|
|
105
|
+
// Primarily for testing / development before host JS interop is built.
|
|
106
|
+
'promise-of': v
|
|
107
|
+
.nativeFn('promise-of', (val: CljValue) => {
|
|
108
|
+
return v.pending(Promise.resolve(val))
|
|
109
|
+
})
|
|
110
|
+
.doc(
|
|
111
|
+
'Wraps val in an immediately-resolving pending value. Useful for testing async composition.',
|
|
112
|
+
[['val']]
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
// (all pendings) → CljPending of a vector of all resolved values.
|
|
116
|
+
// Accepts any seqable (vector, list, lazy-seq, cons, nil); non-pending items resolve immediately.
|
|
117
|
+
// If any input rejects, the result pending rejects with that error.
|
|
118
|
+
all: v
|
|
119
|
+
.nativeFn('all', (val: CljValue) => {
|
|
120
|
+
const items: CljValue[] = val.kind === 'nil' ? [] : toSeq(val)
|
|
121
|
+
const promises = items.map((item) =>
|
|
122
|
+
item.kind === 'pending' ? item.promise : Promise.resolve(item)
|
|
123
|
+
)
|
|
124
|
+
return v.pending(
|
|
125
|
+
Promise.all(promises).then((results) => v.vector(results))
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
.doc(
|
|
129
|
+
'Returns a pending that resolves with a vector of all results when every input resolves.',
|
|
130
|
+
[['pendings']]
|
|
131
|
+
),
|
|
132
|
+
}
|
package/src/core/stdlib/atoms.ts
CHANGED
|
@@ -1,76 +1,311 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { v } from '../factories'
|
|
4
|
+
import { printString } from '../printer'
|
|
5
|
+
import { realizeDelay } from '../transformations'
|
|
6
|
+
import type {
|
|
7
|
+
CljAtom,
|
|
8
|
+
CljFunction,
|
|
9
|
+
CljNativeFunction,
|
|
10
|
+
CljValue,
|
|
11
|
+
Env,
|
|
12
|
+
} from '../types'
|
|
13
|
+
|
|
14
|
+
function validateAtom(
|
|
15
|
+
a: CljAtom,
|
|
16
|
+
newVal: CljValue,
|
|
17
|
+
ctx: import('../types').EvaluationContext,
|
|
18
|
+
callEnv: Env
|
|
19
|
+
) {
|
|
20
|
+
if (a.validator && is.aFunction(a.validator)) {
|
|
21
|
+
const result = ctx.applyFunction(a.validator, [newVal], callEnv)
|
|
22
|
+
if (is.falsy(result)) {
|
|
23
|
+
throw new EvaluationError('Invalid reference state', { newVal })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function notifyWatches(a: CljAtom, oldVal: CljValue, newVal: CljValue) {
|
|
29
|
+
if (a.watches) {
|
|
30
|
+
for (const [, { key, fn, ctx, callEnv }] of a.watches) {
|
|
31
|
+
ctx.applyFunction(
|
|
32
|
+
fn as CljFunction | CljNativeFunction,
|
|
33
|
+
[key, { kind: 'atom', value: newVal } as CljValue, oldVal, newVal],
|
|
34
|
+
callEnv
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
11
39
|
|
|
12
40
|
export const atomFunctions: Record<string, CljValue> = {
|
|
13
|
-
atom:
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
})
|
|
17
|
-
'Returns a new atom holding the given value.',
|
|
18
|
-
[['value']]
|
|
19
|
-
),
|
|
41
|
+
atom: v
|
|
42
|
+
.nativeFn('atom', function atom(value: CljValue) {
|
|
43
|
+
return v.atom(value)
|
|
44
|
+
})
|
|
45
|
+
.doc('Returns a new atom holding the given value.', [['value']]),
|
|
20
46
|
|
|
21
|
-
deref:
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
if (
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
deref: v
|
|
48
|
+
.nativeFn('deref', function deref(value: CljValue) {
|
|
49
|
+
if (is.atom(value)) return value.value
|
|
50
|
+
if (is.volatile(value)) return value.value
|
|
51
|
+
if (is.reduced(value)) return value.value
|
|
52
|
+
if (is.delay(value)) return realizeDelay(value)
|
|
53
|
+
// --- ASYNC (experimental) ---
|
|
54
|
+
if (value.kind === 'pending') {
|
|
55
|
+
throw EvaluationError.atArg(
|
|
56
|
+
'@ on a pending value requires an (async ...) context. Use (async @x) or compose with then/catch.',
|
|
57
|
+
{ value },
|
|
58
|
+
0
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
// --- END ASYNC ---
|
|
62
|
+
throw EvaluationError.atArg(
|
|
63
|
+
`deref expects an atom, volatile, reduced, or delay value, got ${value.kind}`,
|
|
64
|
+
{ value },
|
|
65
|
+
0
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
.doc(
|
|
69
|
+
'Returns the wrapped value from an atom, volatile, reduced, or delay value.',
|
|
70
|
+
[['value']]
|
|
71
|
+
),
|
|
31
72
|
|
|
32
|
-
'swap!':
|
|
33
|
-
|
|
73
|
+
'swap!': v
|
|
74
|
+
.nativeFnCtx(
|
|
34
75
|
'swap!',
|
|
35
|
-
(
|
|
76
|
+
function swap(
|
|
36
77
|
ctx,
|
|
37
78
|
callEnv: Env,
|
|
38
79
|
atomVal: CljValue,
|
|
39
80
|
fn: CljValue,
|
|
40
81
|
...extraArgs: CljValue[]
|
|
41
|
-
)
|
|
42
|
-
if (!
|
|
43
|
-
throw EvaluationError.atArg(
|
|
82
|
+
) {
|
|
83
|
+
if (!is.atom(atomVal)) {
|
|
84
|
+
throw EvaluationError.atArg(
|
|
85
|
+
`swap! expects an atom as its first argument, got ${atomVal.kind}`,
|
|
86
|
+
{ atomVal },
|
|
87
|
+
0
|
|
88
|
+
)
|
|
44
89
|
}
|
|
45
|
-
if (!
|
|
46
|
-
throw EvaluationError.atArg(
|
|
90
|
+
if (!is.aFunction(fn)) {
|
|
91
|
+
throw EvaluationError.atArg(
|
|
92
|
+
`swap! expects a function as its second argument, got ${fn.kind}`,
|
|
93
|
+
{ fn },
|
|
94
|
+
1
|
|
95
|
+
)
|
|
47
96
|
}
|
|
48
|
-
const
|
|
49
|
-
|
|
97
|
+
const a = atomVal as CljAtom
|
|
98
|
+
const oldVal = a.value
|
|
99
|
+
const newVal = ctx.applyFunction(fn, [oldVal, ...extraArgs], callEnv)
|
|
100
|
+
validateAtom(a, newVal, ctx, callEnv)
|
|
101
|
+
a.value = newVal
|
|
102
|
+
notifyWatches(a, oldVal, newVal)
|
|
103
|
+
return newVal
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
.doc(
|
|
107
|
+
'Applies fn to the current value of the atom, replacing the current value with the result. Returns the new value.',
|
|
108
|
+
[['atomVal', 'fn', '&', 'extraArgs']]
|
|
109
|
+
),
|
|
110
|
+
|
|
111
|
+
'reset!': v
|
|
112
|
+
.nativeFnCtx(
|
|
113
|
+
'reset!',
|
|
114
|
+
function reset(ctx, callEnv: Env, atomVal: CljValue, newVal: CljValue) {
|
|
115
|
+
if (!is.atom(atomVal)) {
|
|
116
|
+
throw EvaluationError.atArg(
|
|
117
|
+
`reset! expects an atom as its first argument, got ${atomVal.kind}`,
|
|
118
|
+
{ atomVal },
|
|
119
|
+
0
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
const a = atomVal as CljAtom
|
|
123
|
+
const oldVal = a.value
|
|
124
|
+
validateAtom(a, newVal, ctx, callEnv)
|
|
125
|
+
a.value = newVal
|
|
126
|
+
notifyWatches(a, oldVal, newVal)
|
|
50
127
|
return newVal
|
|
51
128
|
}
|
|
129
|
+
)
|
|
130
|
+
.doc('Sets the value of the atom to newVal and returns the new value.', [
|
|
131
|
+
['atomVal', 'newVal'],
|
|
132
|
+
]),
|
|
133
|
+
|
|
134
|
+
'atom?': v
|
|
135
|
+
.nativeFn('atom?', function isAtomPredicate(value: CljValue) {
|
|
136
|
+
return v.boolean(is.atom(value))
|
|
137
|
+
})
|
|
138
|
+
.doc('Returns true if the value is an atom, false otherwise.', [['value']]),
|
|
139
|
+
|
|
140
|
+
'swap-vals!': v
|
|
141
|
+
.nativeFnCtx(
|
|
142
|
+
'swap-vals!',
|
|
143
|
+
function swapVals(
|
|
144
|
+
ctx,
|
|
145
|
+
callEnv: Env,
|
|
146
|
+
atomVal: CljValue,
|
|
147
|
+
fn: CljValue,
|
|
148
|
+
...extraArgs: CljValue[]
|
|
149
|
+
) {
|
|
150
|
+
if (!is.atom(atomVal)) {
|
|
151
|
+
throw EvaluationError.atArg(
|
|
152
|
+
`swap-vals! expects an atom, got ${printString(atomVal)}`,
|
|
153
|
+
{ atomVal },
|
|
154
|
+
0
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
if (!is.aFunction(fn)) {
|
|
158
|
+
throw EvaluationError.atArg(
|
|
159
|
+
`swap-vals! expects a function, got ${printString(fn)}`,
|
|
160
|
+
{ fn },
|
|
161
|
+
1
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
const oldVal = atomVal.value
|
|
165
|
+
const newVal = ctx.applyFunction(fn, [oldVal, ...extraArgs], callEnv)
|
|
166
|
+
atomVal.value = newVal
|
|
167
|
+
return v.vector([oldVal, newVal])
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
.doc(
|
|
171
|
+
'Atomically swaps the value of atom to be (apply f current-value-of-atom args). Returns [old new].',
|
|
172
|
+
[['atom', 'f', '&', 'args']]
|
|
173
|
+
),
|
|
174
|
+
|
|
175
|
+
'reset-vals!': v
|
|
176
|
+
.nativeFn(
|
|
177
|
+
'reset-vals!',
|
|
178
|
+
function resetVals(atomVal: CljValue, newVal: CljValue) {
|
|
179
|
+
if (!is.atom(atomVal)) {
|
|
180
|
+
throw EvaluationError.atArg(
|
|
181
|
+
`reset-vals! expects an atom, got ${printString(atomVal)}`,
|
|
182
|
+
{ atomVal },
|
|
183
|
+
0
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
const oldVal = atomVal.value
|
|
187
|
+
atomVal.value = newVal
|
|
188
|
+
return v.vector([oldVal, newVal])
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
.doc('Sets the value of atom to newVal. Returns [old new].', [
|
|
192
|
+
['atom', 'newval'],
|
|
193
|
+
]),
|
|
194
|
+
|
|
195
|
+
'compare-and-set!': v
|
|
196
|
+
.nativeFn(
|
|
197
|
+
'compare-and-set!',
|
|
198
|
+
function compareAndSet(
|
|
199
|
+
atomVal: CljValue,
|
|
200
|
+
oldv: CljValue,
|
|
201
|
+
newv: CljValue
|
|
202
|
+
) {
|
|
203
|
+
if (!is.atom(atomVal)) {
|
|
204
|
+
throw EvaluationError.atArg(
|
|
205
|
+
`compare-and-set! expects an atom, got ${printString(atomVal)}`,
|
|
206
|
+
{ atomVal },
|
|
207
|
+
0
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
if (is.equal(atomVal.value, oldv)) {
|
|
211
|
+
atomVal.value = newv
|
|
212
|
+
return v.boolean(true)
|
|
213
|
+
}
|
|
214
|
+
return v.boolean(false)
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
.doc(
|
|
218
|
+
'Atomically sets the value of atom to newval if and only if the current value of the atom is identical to oldval. Returns true if set happened, else false.',
|
|
219
|
+
[['atom', 'oldval', 'newval']]
|
|
220
|
+
),
|
|
221
|
+
|
|
222
|
+
'add-watch': v
|
|
223
|
+
.nativeFnCtx(
|
|
224
|
+
'add-watch',
|
|
225
|
+
function addWatch(
|
|
226
|
+
ctx,
|
|
227
|
+
callEnv: Env,
|
|
228
|
+
atomVal: CljValue,
|
|
229
|
+
key: CljValue,
|
|
230
|
+
fn: CljValue
|
|
231
|
+
) {
|
|
232
|
+
if (!is.atom(atomVal)) {
|
|
233
|
+
throw EvaluationError.atArg(
|
|
234
|
+
`add-watch expects an atom, got ${printString(atomVal)}`,
|
|
235
|
+
{ atomVal },
|
|
236
|
+
0
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
if (!is.aFunction(fn)) {
|
|
240
|
+
throw EvaluationError.atArg(
|
|
241
|
+
`add-watch expects a function, got ${printString(fn)}`,
|
|
242
|
+
{ fn },
|
|
243
|
+
2
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
const a = atomVal as CljAtom
|
|
247
|
+
if (!a.watches) a.watches = new Map()
|
|
248
|
+
// Store a wrapper that calls the user fn through the evaluator
|
|
249
|
+
a.watches.set(printString(key), { key, fn, ctx, callEnv })
|
|
250
|
+
return atomVal
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
.doc(
|
|
254
|
+
'Adds a watch function to an atom. The watch fn must be a fn of 4 args: a key, the atom, its old-state, its new-state.',
|
|
255
|
+
[['atom', 'key', 'fn']]
|
|
52
256
|
),
|
|
53
|
-
'Applies fn to the current value of the atom, replacing the current value with the result. Returns the new value.',
|
|
54
|
-
[['atomVal', 'fn', '&', 'extraArgs']]
|
|
55
|
-
),
|
|
56
257
|
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
258
|
+
'remove-watch': v
|
|
259
|
+
.nativeFn(
|
|
260
|
+
'remove-watch',
|
|
261
|
+
function removeWatch(atomVal: CljValue, key: CljValue) {
|
|
262
|
+
if (!is.atom(atomVal)) {
|
|
263
|
+
throw EvaluationError.atArg(
|
|
264
|
+
`remove-watch expects an atom, got ${printString(atomVal)}`,
|
|
265
|
+
{ atomVal },
|
|
266
|
+
0
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
const a = atomVal as CljAtom
|
|
270
|
+
if (a.watches) a.watches.delete(printString(key))
|
|
271
|
+
return atomVal
|
|
61
272
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}),
|
|
65
|
-
'Sets the value of the atom to newVal and returns the new value.',
|
|
66
|
-
[['atomVal', 'newVal']]
|
|
67
|
-
),
|
|
273
|
+
)
|
|
274
|
+
.doc('Removes a watch (set by add-watch) from an atom.', [['atom', 'key']]),
|
|
68
275
|
|
|
69
|
-
'
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
276
|
+
'set-validator!': v
|
|
277
|
+
.nativeFnCtx(
|
|
278
|
+
'set-validator!',
|
|
279
|
+
function setValidator(
|
|
280
|
+
_ctx,
|
|
281
|
+
_callEnv: Env,
|
|
282
|
+
atomVal: CljValue,
|
|
283
|
+
fn: CljValue
|
|
284
|
+
) {
|
|
285
|
+
if (!is.atom(atomVal)) {
|
|
286
|
+
throw EvaluationError.atArg(
|
|
287
|
+
`set-validator! expects an atom, got ${printString(atomVal)}`,
|
|
288
|
+
{ atomVal },
|
|
289
|
+
0
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
if (fn.kind === 'nil') {
|
|
293
|
+
;(atomVal as CljAtom).validator = undefined
|
|
294
|
+
return v.nil()
|
|
295
|
+
}
|
|
296
|
+
if (!is.aFunction(fn)) {
|
|
297
|
+
throw EvaluationError.atArg(
|
|
298
|
+
`set-validator! expects a function or nil, got ${printString(fn)}`,
|
|
299
|
+
{ fn },
|
|
300
|
+
1
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
;(atomVal as CljAtom).validator = fn
|
|
304
|
+
return v.nil()
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
.doc(
|
|
308
|
+
'Sets the validator-fn for an atom. fn must be nil or a side-effect-free fn of one argument.',
|
|
309
|
+
[['atom', 'fn']]
|
|
310
|
+
),
|
|
76
311
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CljThrownSignal, EvaluationError } from '../errors'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { v } from '../factories'
|
|
3
|
+
import { is } from '../assertions'
|
|
4
4
|
import type { CljValue } from '../types'
|
|
5
5
|
|
|
6
6
|
export const errorFunctions = {
|
|
7
|
-
throw:
|
|
8
|
-
|
|
7
|
+
throw: v
|
|
8
|
+
.nativeFn('throw', function throwImpl(...args: CljValue[]) {
|
|
9
9
|
if (args.length !== 1) {
|
|
10
10
|
throw new EvaluationError(
|
|
11
11
|
`throw requires exactly 1 argument, got ${args.length}`,
|
|
@@ -13,13 +13,14 @@ export const errorFunctions = {
|
|
|
13
13
|
)
|
|
14
14
|
}
|
|
15
15
|
throw new CljThrownSignal(args[0])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
})
|
|
17
|
+
.doc(
|
|
18
|
+
'Throws a value as an exception. The value may be any CljValue; maps are idiomatic.',
|
|
19
|
+
[['value']]
|
|
20
|
+
),
|
|
20
21
|
|
|
21
|
-
'ex-info':
|
|
22
|
-
|
|
22
|
+
'ex-info': v
|
|
23
|
+
.nativeFn('ex-info', function exInfoImpl(...args: CljValue[]) {
|
|
23
24
|
if (args.length < 2) {
|
|
24
25
|
throw new EvaluationError(
|
|
25
26
|
`ex-info requires at least 2 arguments, got ${args.length}`,
|
|
@@ -27,55 +28,58 @@ export const errorFunctions = {
|
|
|
27
28
|
)
|
|
28
29
|
}
|
|
29
30
|
const [msg, data, cause] = args
|
|
30
|
-
if (
|
|
31
|
-
throw new EvaluationError(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
31
|
+
if (!is.string(msg)) {
|
|
32
|
+
throw new EvaluationError('ex-info: first argument must be a string', {
|
|
33
|
+
msg,
|
|
34
|
+
})
|
|
35
35
|
}
|
|
36
36
|
const entries: [CljValue, CljValue][] = [
|
|
37
|
-
[
|
|
38
|
-
[
|
|
37
|
+
[v.keyword(':message'), msg],
|
|
38
|
+
[v.keyword(':data'), data],
|
|
39
39
|
]
|
|
40
40
|
if (cause !== undefined) {
|
|
41
|
-
entries.push([
|
|
41
|
+
entries.push([v.keyword(':cause'), cause])
|
|
42
42
|
}
|
|
43
|
-
return
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
return v.map(entries)
|
|
44
|
+
})
|
|
45
|
+
.doc(
|
|
46
|
+
'Creates an error map with :message and :data keys. Optionally accepts a :cause.',
|
|
47
|
+
[
|
|
48
|
+
['msg', 'data'],
|
|
49
|
+
['msg', 'data', 'cause'],
|
|
50
|
+
]
|
|
51
|
+
),
|
|
48
52
|
|
|
49
|
-
'ex-message':
|
|
50
|
-
|
|
53
|
+
'ex-message': v
|
|
54
|
+
.nativeFn('ex-message', function exMessageImpl(...args: CljValue[]) {
|
|
51
55
|
const [e] = args
|
|
52
|
-
if (!
|
|
53
|
-
const entry = e.entries.find(([k])
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (!is.map(e)) return v.nil()
|
|
57
|
+
const entry = e.entries.find(function findMessageKey([k]) {
|
|
58
|
+
return is.keyword(k) && k.name === ':message'
|
|
59
|
+
})
|
|
60
|
+
return entry ? entry[1] : v.nil()
|
|
61
|
+
})
|
|
62
|
+
.doc('Returns the :message of an error map, or nil.', [['e']]),
|
|
59
63
|
|
|
60
|
-
'ex-data':
|
|
61
|
-
|
|
64
|
+
'ex-data': v
|
|
65
|
+
.nativeFn('ex-data', function exDataImpl(...args: CljValue[]) {
|
|
62
66
|
const [e] = args
|
|
63
|
-
if (!
|
|
64
|
-
const entry = e.entries.find(([k])
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (!is.map(e)) return v.nil()
|
|
68
|
+
const entry = e.entries.find(function findDataKey([k]) {
|
|
69
|
+
return is.keyword(k) && k.name === ':data'
|
|
70
|
+
})
|
|
71
|
+
return entry ? entry[1] : v.nil()
|
|
72
|
+
})
|
|
73
|
+
.doc('Returns the :data map of an error map, or nil.', [['e']]),
|
|
70
74
|
|
|
71
|
-
'ex-cause':
|
|
72
|
-
|
|
75
|
+
'ex-cause': v
|
|
76
|
+
.nativeFn('ex-cause', function exCauseImpl(...args: CljValue[]) {
|
|
73
77
|
const [e] = args
|
|
74
|
-
if (!
|
|
75
|
-
const entry = e.entries.find(([k])
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (!is.map(e)) return v.nil()
|
|
79
|
+
const entry = e.entries.find(function findCauseKey([k]) {
|
|
80
|
+
return is.keyword(k) && k.name === ':cause'
|
|
81
|
+
})
|
|
82
|
+
return entry ? entry[1] : v.nil()
|
|
83
|
+
})
|
|
84
|
+
.doc('Returns the :cause of an error map, or nil.', [['e']]),
|
|
81
85
|
}
|