conjure-js 0.0.13 → 0.0.14
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 +2328 -2089
- package/dist-vite-plugin/index.mjs +2327 -2088
- package/package.json +1 -1
- package/src/bin/version.ts +1 -1
- package/src/core/assertions.ts +10 -3
- package/src/core/bootstrap.ts +7 -23
- package/src/core/compiler/binding.ts +164 -0
- package/src/core/compiler/callable.ts +41 -0
- package/src/core/compiler/compile-env.ts +40 -0
- package/src/core/compiler/control-flow.ts +79 -0
- package/src/core/compiler/index.ts +121 -0
- package/src/core/env.ts +4 -4
- package/src/core/errors.ts +1 -0
- package/src/core/evaluator/apply.ts +7 -3
- package/src/core/evaluator/arity.ts +16 -6
- package/src/core/evaluator/async-evaluator.ts +68 -89
- package/src/core/evaluator/collections.ts +9 -4
- package/src/core/evaluator/destructure.ts +45 -55
- package/src/core/evaluator/dispatch.ts +21 -24
- package/src/core/evaluator/evaluate.ts +14 -2
- package/src/core/evaluator/expand.ts +5 -7
- package/src/core/evaluator/js-interop.ts +46 -33
- package/src/core/evaluator/quasiquote.ts +7 -11
- package/src/core/evaluator/recur-check.ts +1 -1
- package/src/core/evaluator/special-forms.ts +18 -38
- package/src/core/index.ts +1 -1
- package/src/core/keywords.ts +105 -0
- package/src/core/modules/core/index.ts +131 -0
- package/src/core/{stdlib → modules/core/stdlib}/arithmetic.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/async-fns.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/atoms.ts +7 -7
- package/src/core/{stdlib → modules/core/stdlib}/errors.ts +4 -4
- package/src/core/{stdlib → modules/core/stdlib}/hof.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/lazy.ts +4 -4
- package/src/core/{stdlib → modules/core/stdlib}/maps-sets.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/meta.ts +5 -5
- package/src/core/{stdlib → modules/core/stdlib}/predicates.ts +7 -7
- package/src/core/modules/core/stdlib/print.ts +108 -0
- package/src/core/{stdlib → modules/core/stdlib}/regex.ts +5 -5
- package/src/core/{stdlib → modules/core/stdlib}/seq.ts +7 -7
- package/src/core/{stdlib → modules/core/stdlib}/strings.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/transducers.ts +6 -6
- package/src/core/{stdlib → modules/core/stdlib}/utils.ts +10 -10
- package/src/core/{stdlib → modules/core/stdlib}/vars.ts +4 -4
- package/src/core/{stdlib → modules/core/stdlib}/vectors.ts +6 -6
- package/src/core/modules/js/index.ts +402 -0
- package/src/core/ns-forms.ts +25 -17
- package/src/core/positions.ts +22 -2
- package/src/core/printer.ts +162 -53
- package/src/core/reader.ts +25 -22
- package/src/core/registry.ts +10 -10
- package/src/core/runtime.ts +23 -23
- package/src/core/session.ts +17 -7
- package/src/core/tokenizer.ts +14 -4
- package/src/core/transformations.ts +48 -29
- package/src/core/types.ts +57 -81
- package/src/core/core-module.ts +0 -303
- package/src/core/stdlib/js-namespace.ts +0 -344
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
3
|
import { printString } from '../printer'
|
|
4
|
-
import {
|
|
4
|
+
import { maybeHydrateErrorPos } from '../positions'
|
|
5
5
|
import type {
|
|
6
6
|
CljList,
|
|
7
7
|
CljValue,
|
|
@@ -12,7 +12,10 @@ import type {
|
|
|
12
12
|
|
|
13
13
|
import { evaluateSpecialForm } from './special-forms'
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const LIST_HEAD_POS = 0
|
|
16
|
+
const LIST_BODY_POS = 1
|
|
17
|
+
|
|
18
|
+
export function dispatchMultiMethod(
|
|
16
19
|
mm: CljMultiMethod,
|
|
17
20
|
args: CljValue[],
|
|
18
21
|
ctx: EvaluationContext,
|
|
@@ -45,39 +48,33 @@ export function evaluateList(
|
|
|
45
48
|
if (list.value.length === 0) {
|
|
46
49
|
return list
|
|
47
50
|
}
|
|
48
|
-
const
|
|
51
|
+
const head = list.value[LIST_HEAD_POS]
|
|
49
52
|
|
|
50
|
-
if (is.specialForm(
|
|
51
|
-
return evaluateSpecialForm(
|
|
53
|
+
if (is.specialForm(head)) {
|
|
54
|
+
return evaluateSpecialForm(head.name, list, env, ctx)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
const
|
|
57
|
+
const evaledHead = ctx.evaluate(head, env)
|
|
55
58
|
|
|
56
|
-
if (is.multiMethod(
|
|
57
|
-
const args = list.value
|
|
58
|
-
|
|
59
|
+
if (is.multiMethod(evaledHead)) {
|
|
60
|
+
const args = list.value
|
|
61
|
+
.slice(LIST_BODY_POS)
|
|
62
|
+
.map((arg) => ctx.evaluate(arg, env))
|
|
63
|
+
return dispatchMultiMethod(evaledHead, args, ctx, env)
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
if (!is.callable(
|
|
62
|
-
const name = is.symbol(
|
|
66
|
+
if (!is.callable(evaledHead)) {
|
|
67
|
+
const name = is.symbol(head) ? head.name : printString(head)
|
|
63
68
|
throw new EvaluationError(`${name} is not callable`, { list, env })
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
const args = list.value
|
|
71
|
+
const args = list.value
|
|
72
|
+
.slice(LIST_BODY_POS)
|
|
73
|
+
.map((arg) => ctx.evaluate(arg, env))
|
|
67
74
|
try {
|
|
68
|
-
return ctx.applyCallable(
|
|
75
|
+
return ctx.applyCallable(evaledHead, args, env)
|
|
69
76
|
} catch (e) {
|
|
70
|
-
|
|
71
|
-
e instanceof EvaluationError &&
|
|
72
|
-
e.data?.argIndex !== undefined &&
|
|
73
|
-
!e.pos
|
|
74
|
-
) {
|
|
75
|
-
const argForm = list.value[(e.data.argIndex as number) + 1]
|
|
76
|
-
if (argForm) {
|
|
77
|
-
const pos = getPos(argForm)
|
|
78
|
-
if (pos) e.pos = pos
|
|
79
|
-
}
|
|
80
|
-
}
|
|
77
|
+
maybeHydrateErrorPos(e, list)
|
|
81
78
|
throw e
|
|
82
79
|
}
|
|
83
80
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluator - Core entrypoint
|
|
3
|
+
* Handles the evaluation of a single expression.
|
|
4
|
+
* Delegates most of the work to domain handlers.
|
|
5
|
+
* Uses the compiler to compile the expression to a closure when possible.
|
|
6
|
+
* The interpreter is the source of truth for the semantics of the language.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { compile } from '../compiler'
|
|
1
10
|
import { derefValue, getNamespaceEnv, lookup } from '../env'
|
|
2
11
|
import { EvaluationError } from '../errors'
|
|
3
12
|
import { v } from '../factories'
|
|
13
|
+
import { valueKeywords } from '../keywords.ts'
|
|
4
14
|
import { getPos } from '../positions'
|
|
5
|
-
import { valueKeywords } from '../types'
|
|
6
|
-
|
|
7
15
|
import type { CljValue, Env, EvaluationContext } from '../types'
|
|
8
16
|
import { evaluateMap, evaluateSet, evaluateVector } from './collections'
|
|
9
17
|
import { evaluateList } from './dispatch'
|
|
@@ -24,6 +32,10 @@ export function evaluateWithContext(
|
|
|
24
32
|
ctx: EvaluationContext
|
|
25
33
|
): CljValue {
|
|
26
34
|
try {
|
|
35
|
+
const compiled = compile(expr)
|
|
36
|
+
if (compiled !== null) {
|
|
37
|
+
return compiled(env, ctx)
|
|
38
|
+
}
|
|
27
39
|
switch (expr.kind) {
|
|
28
40
|
// self-evaluating forms
|
|
29
41
|
case valueKeywords.number:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { is } from '../assertions'
|
|
2
2
|
import { derefValue, getNamespaceEnv, tryLookup } from '../env'
|
|
3
|
-
import {
|
|
3
|
+
import { v } from '../factories'
|
|
4
4
|
import type { CljValue, Env, EvaluationContext } from '../types'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -31,7 +31,7 @@ export function macroExpandAllWithContext(
|
|
|
31
31
|
)
|
|
32
32
|
return expanded.every((e, i) => e === form.value[i])
|
|
33
33
|
? form
|
|
34
|
-
:
|
|
34
|
+
: v.vector(expanded)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Maps: expand each key and value
|
|
@@ -47,7 +47,7 @@ export function macroExpandAllWithContext(
|
|
|
47
47
|
([k, v], i) => k === form.entries[i][0] && v === form.entries[i][1]
|
|
48
48
|
)
|
|
49
49
|
? form
|
|
50
|
-
:
|
|
50
|
+
: v.map(expanded)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Atoms (number, string, boolean, keyword, nil, symbol, regex, functions, etc.)
|
|
@@ -65,7 +65,7 @@ export function macroExpandAllWithContext(
|
|
|
65
65
|
)
|
|
66
66
|
return expanded.every((e, i) => e === form.value[i])
|
|
67
67
|
? form
|
|
68
|
-
:
|
|
68
|
+
: v.list(expanded)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const name = first.name
|
|
@@ -103,7 +103,5 @@ export function macroExpandAllWithContext(
|
|
|
103
103
|
const expanded = form.value.map((sub) =>
|
|
104
104
|
macroExpandAllWithContext(sub, env, ctx)
|
|
105
105
|
)
|
|
106
|
-
return expanded.every((e, i) => e === form.value[i])
|
|
107
|
-
? form
|
|
108
|
-
: cljList(expanded)
|
|
106
|
+
return expanded.every((e, i) => e === form.value[i]) ? form : v.list(expanded)
|
|
109
107
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
-
import {
|
|
3
|
+
import { v } from '../factories'
|
|
4
4
|
import type { CljList, CljValue, Env, EvaluationContext } from '../types'
|
|
5
5
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -14,12 +14,12 @@ import type { CljList, CljValue, Env, EvaluationContext } from '../types'
|
|
|
14
14
|
* - primitives convert; everything else boxes.
|
|
15
15
|
*/
|
|
16
16
|
export function jsToClj(raw: unknown): CljValue {
|
|
17
|
-
if (raw === null) return
|
|
18
|
-
if (raw === undefined) return
|
|
19
|
-
if (typeof raw === 'number') return
|
|
20
|
-
if (typeof raw === 'string') return
|
|
21
|
-
if (typeof raw === 'boolean') return
|
|
22
|
-
return
|
|
17
|
+
if (raw === null) return v.nil()
|
|
18
|
+
if (raw === undefined) return v.jsValue(undefined)
|
|
19
|
+
if (typeof raw === 'number') return v.number(raw)
|
|
20
|
+
if (typeof raw === 'string') return v.string(raw)
|
|
21
|
+
if (typeof raw === 'boolean') return v.boolean(raw)
|
|
22
|
+
return v.jsValue(raw)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -28,13 +28,13 @@ export function jsToClj(raw: unknown): CljValue {
|
|
|
28
28
|
* have no meaningful JS representation and must be reduced to a primitive first.
|
|
29
29
|
*/
|
|
30
30
|
function mapKeyToString(key: CljValue): string {
|
|
31
|
-
if (
|
|
32
|
-
if (
|
|
33
|
-
if (
|
|
34
|
-
if (
|
|
31
|
+
if (is.string(key)) return key.value
|
|
32
|
+
if (is.keyword(key)) return key.name.slice(1) // strip leading ':'
|
|
33
|
+
if (is.number(key)) return String(key.value)
|
|
34
|
+
if (is.boolean(key)) return String(key.value)
|
|
35
35
|
throw new EvaluationError(
|
|
36
36
|
`cljToJs: map key must be a string, keyword, number, or boolean — ` +
|
|
37
|
-
|
|
37
|
+
`got ${key.kind} (rich keys are not allowed as JS object keys; reduce to a primitive first)`,
|
|
38
38
|
{ key }
|
|
39
39
|
)
|
|
40
40
|
}
|
|
@@ -43,14 +43,24 @@ function mapKeyToString(key: CljValue): string {
|
|
|
43
43
|
* Convert a CljValue to a raw JS value for crossing the interop boundary.
|
|
44
44
|
* Called on each argument passed to `.` and `js/new`.
|
|
45
45
|
*/
|
|
46
|
-
export function cljToJs(
|
|
46
|
+
export function cljToJs(
|
|
47
|
+
val: CljValue,
|
|
48
|
+
ctx: EvaluationContext,
|
|
49
|
+
callEnv: Env
|
|
50
|
+
): unknown {
|
|
47
51
|
switch (val.kind) {
|
|
48
|
-
case 'js-value':
|
|
49
|
-
|
|
50
|
-
case '
|
|
51
|
-
|
|
52
|
-
case '
|
|
53
|
-
|
|
52
|
+
case 'js-value':
|
|
53
|
+
return val.value
|
|
54
|
+
case 'number':
|
|
55
|
+
return val.value
|
|
56
|
+
case 'string':
|
|
57
|
+
return val.value
|
|
58
|
+
case 'boolean':
|
|
59
|
+
return val.value
|
|
60
|
+
case 'nil':
|
|
61
|
+
return null
|
|
62
|
+
case 'keyword':
|
|
63
|
+
return val.name.slice(1) // strip leading ':'
|
|
54
64
|
case 'function':
|
|
55
65
|
case 'native-function': {
|
|
56
66
|
const fn = val
|
|
@@ -90,15 +100,14 @@ export function cljToJs(val: CljValue, ctx: EvaluationContext, callEnv: Env): un
|
|
|
90
100
|
*/
|
|
91
101
|
function extractRawTarget(target: CljValue): unknown {
|
|
92
102
|
switch (target.kind) {
|
|
93
|
-
case 'js-value':
|
|
103
|
+
case 'js-value':
|
|
104
|
+
return target.value
|
|
94
105
|
case 'string':
|
|
95
106
|
case 'number':
|
|
96
|
-
case 'boolean':
|
|
107
|
+
case 'boolean':
|
|
108
|
+
return target.value
|
|
97
109
|
default:
|
|
98
|
-
throw new EvaluationError(
|
|
99
|
-
`cannot use . on ${target.kind}`,
|
|
100
|
-
{ target }
|
|
101
|
-
)
|
|
110
|
+
throw new EvaluationError(`cannot use . on ${target.kind}`, { target })
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
@@ -108,10 +117,9 @@ export function evaluateDot(
|
|
|
108
117
|
ctx: EvaluationContext
|
|
109
118
|
): CljValue {
|
|
110
119
|
if (list.value.length < 3) {
|
|
111
|
-
throw new EvaluationError(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
120
|
+
throw new EvaluationError('. requires at least 2 arguments: (. obj prop)', {
|
|
121
|
+
list,
|
|
122
|
+
})
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
const target = ctx.evaluate(list.value[1], env)
|
|
@@ -141,7 +149,7 @@ export function evaluateDot(
|
|
|
141
149
|
// Functions are bound to their object so that ((. obj method)) works correctly.
|
|
142
150
|
const rawProp = rawObj[propName]
|
|
143
151
|
if (typeof rawProp === 'function') {
|
|
144
|
-
return
|
|
152
|
+
return v.jsValue((rawProp as (...a: unknown[]) => unknown).bind(rawObj))
|
|
145
153
|
}
|
|
146
154
|
return jsToClj(rawProp)
|
|
147
155
|
}
|
|
@@ -157,7 +165,10 @@ export function evaluateDot(
|
|
|
157
165
|
|
|
158
166
|
const cljArgs = list.value.slice(3).map((a) => ctx.evaluate(a, env))
|
|
159
167
|
const jsArgs = cljArgs.map((a) => cljToJs(a, ctx, env))
|
|
160
|
-
const rawResult = (method as (...args: unknown[]) => unknown).apply(
|
|
168
|
+
const rawResult = (method as (...args: unknown[]) => unknown).apply(
|
|
169
|
+
rawObj,
|
|
170
|
+
jsArgs
|
|
171
|
+
)
|
|
161
172
|
return jsToClj(rawResult)
|
|
162
173
|
}
|
|
163
174
|
|
|
@@ -171,7 +182,9 @@ export function evaluateNew(
|
|
|
171
182
|
ctx: EvaluationContext
|
|
172
183
|
): CljValue {
|
|
173
184
|
if (list.value.length < 2) {
|
|
174
|
-
throw new EvaluationError('js/new requires a constructor argument', {
|
|
185
|
+
throw new EvaluationError('js/new requires a constructor argument', {
|
|
186
|
+
list,
|
|
187
|
+
})
|
|
175
188
|
}
|
|
176
189
|
|
|
177
190
|
const cls = ctx.evaluate(list.value[1], env)
|
|
@@ -185,5 +198,5 @@ export function evaluateNew(
|
|
|
185
198
|
const cljArgs = list.value.slice(2).map((a) => ctx.evaluate(a, env))
|
|
186
199
|
const jsArgs = cljArgs.map((a) => cljToJs(a, ctx, env))
|
|
187
200
|
const ctor = cls.value as new (...args: unknown[]) => unknown
|
|
188
|
-
return
|
|
201
|
+
return v.jsValue(new ctor(...jsArgs))
|
|
189
202
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
-
import {
|
|
4
|
-
import { toSeq } from '../transformations'
|
|
3
|
+
import { v } from '../factories'
|
|
5
4
|
import { makeGensym } from '../gensym'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type EvaluationContext,
|
|
10
|
-
valueKeywords,
|
|
11
|
-
} from '../types'
|
|
5
|
+
import { valueKeywords } from '../keywords'
|
|
6
|
+
import { toSeq } from '../transformations'
|
|
7
|
+
import { type CljValue, type Env, type EvaluationContext } from '../types'
|
|
12
8
|
|
|
13
9
|
export function evaluateQuasiquote(
|
|
14
10
|
form: CljValue,
|
|
@@ -58,7 +54,7 @@ export function evaluateQuasiquote(
|
|
|
58
54
|
// Otherwise, recursively evaluate the quasiquote
|
|
59
55
|
elements.push(evaluateQuasiquote(elem, env, autoGensyms, ctx))
|
|
60
56
|
}
|
|
61
|
-
return isAList ?
|
|
57
|
+
return isAList ? v.list(elements) : v.vector(elements)
|
|
62
58
|
}
|
|
63
59
|
case valueKeywords.map: {
|
|
64
60
|
const entries: [CljValue, CljValue][] = []
|
|
@@ -67,7 +63,7 @@ export function evaluateQuasiquote(
|
|
|
67
63
|
const evaluatedValue = evaluateQuasiquote(value, env, autoGensyms, ctx)
|
|
68
64
|
entries.push([evaluatedKey, evaluatedValue])
|
|
69
65
|
}
|
|
70
|
-
return
|
|
66
|
+
return v.map(entries)
|
|
71
67
|
}
|
|
72
68
|
case valueKeywords.number:
|
|
73
69
|
case valueKeywords.string:
|
|
@@ -83,7 +79,7 @@ export function evaluateQuasiquote(
|
|
|
83
79
|
if (!autoGensyms.has(form.name)) {
|
|
84
80
|
autoGensyms.set(form.name, makeGensym(form.name.slice(0, -1)))
|
|
85
81
|
}
|
|
86
|
-
return
|
|
82
|
+
return v.symbol(autoGensyms.get(form.name)!)
|
|
87
83
|
}
|
|
88
84
|
return form
|
|
89
85
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
+
import { specialFormKeywords } from '../keywords.ts'
|
|
3
4
|
import type { CljValue } from '../types'
|
|
4
|
-
import { specialFormKeywords } from './special-forms'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Asserts that every `recur` form in `body` appears strictly in tail position.
|
|
@@ -13,6 +13,7 @@ import { v } from '../factories'
|
|
|
13
13
|
// --- ASYNC (experimental) ---
|
|
14
14
|
import { createAsyncEvalCtx } from './async-evaluator'
|
|
15
15
|
// --- END ASYNC ---
|
|
16
|
+
import { specialFormKeywords } from '../keywords.ts'
|
|
16
17
|
import { getLineCol, getPos } from '../positions'
|
|
17
18
|
import type {
|
|
18
19
|
CljFunction,
|
|
@@ -27,14 +28,16 @@ import type {
|
|
|
27
28
|
} from '../types'
|
|
28
29
|
import { parseArities, RecurSignal } from './arity'
|
|
29
30
|
import { destructureBindings } from './destructure'
|
|
30
|
-
import { evaluateDot, evaluateNew } from './js-interop'
|
|
31
|
-
import { evaluateQuasiquote } from './quasiquote'
|
|
32
|
-
import { assertRecurInTailPosition } from './recur-check'
|
|
33
31
|
import {
|
|
34
32
|
matchesDiscriminator,
|
|
35
33
|
parseTryStructure,
|
|
36
34
|
validateBindingVector,
|
|
37
35
|
} from './form-parsers'
|
|
36
|
+
import { evaluateDot, evaluateNew } from './js-interop'
|
|
37
|
+
import { evaluateQuasiquote } from './quasiquote'
|
|
38
|
+
import { assertRecurInTailPosition } from './recur-check'
|
|
39
|
+
|
|
40
|
+
import { compile } from '../compiler/index.ts'
|
|
38
41
|
|
|
39
42
|
function hasDynamicMeta(meta: CljMap | undefined): boolean {
|
|
40
43
|
if (!meta) return false
|
|
@@ -51,36 +54,6 @@ function hasDynamicMeta(meta: CljMap | undefined): boolean {
|
|
|
51
54
|
return false
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
export const specialFormKeywords = {
|
|
55
|
-
quote: 'quote',
|
|
56
|
-
def: 'def',
|
|
57
|
-
if: 'if',
|
|
58
|
-
do: 'do',
|
|
59
|
-
let: 'let',
|
|
60
|
-
fn: 'fn',
|
|
61
|
-
defmacro: 'defmacro',
|
|
62
|
-
quasiquote: 'quasiquote',
|
|
63
|
-
ns: 'ns',
|
|
64
|
-
loop: 'loop',
|
|
65
|
-
recur: 'recur',
|
|
66
|
-
defmulti: 'defmulti',
|
|
67
|
-
defmethod: 'defmethod',
|
|
68
|
-
try: 'try',
|
|
69
|
-
var: 'var',
|
|
70
|
-
binding: 'binding',
|
|
71
|
-
'set!': 'set!',
|
|
72
|
-
letfn: 'letfn',
|
|
73
|
-
delay: 'delay',
|
|
74
|
-
'lazy-seq': 'lazy-seq',
|
|
75
|
-
// --- ASYNC (experimental) ---
|
|
76
|
-
async: 'async',
|
|
77
|
-
// --- END ASYNC ---
|
|
78
|
-
// --- JS INTEROP ---
|
|
79
|
-
'.': '.',
|
|
80
|
-
'js/new': 'js/new',
|
|
81
|
-
// --- END JS INTEROP ---
|
|
82
|
-
} as const
|
|
83
|
-
|
|
84
57
|
function keywordToDispatchFn(kw: CljKeyword): CljNativeFunction {
|
|
85
58
|
return v.nativeFn(`kw:${kw.name}`, (...args: CljValue[]) => {
|
|
86
59
|
const target = args[0]
|
|
@@ -95,10 +68,7 @@ function evaluateTry(
|
|
|
95
68
|
env: Env,
|
|
96
69
|
ctx: EvaluationContext
|
|
97
70
|
): CljValue {
|
|
98
|
-
const { bodyForms, catchClauses, finallyForms } = parseTryStructure(
|
|
99
|
-
list,
|
|
100
|
-
env
|
|
101
|
-
)
|
|
71
|
+
const { bodyForms, catchClauses, finallyForms } = parseTryStructure(list, env)
|
|
102
72
|
|
|
103
73
|
let result: CljValue = v.nil()
|
|
104
74
|
let pendingThrow: unknown = null
|
|
@@ -294,13 +264,23 @@ function evaluateFn(
|
|
|
294
264
|
// (fn name [...] ...) — optional name symbol before the param vector/arities
|
|
295
265
|
let fnName: string | undefined
|
|
296
266
|
let arityForms = rest
|
|
297
|
-
if (rest[0]
|
|
267
|
+
if (rest[0] && is.symbol(rest[0])) {
|
|
298
268
|
fnName = rest[0].name
|
|
299
269
|
arityForms = rest.slice(1)
|
|
300
270
|
}
|
|
301
271
|
const arities = parseArities(arityForms, env)
|
|
302
272
|
for (const arity of arities) {
|
|
303
273
|
assertRecurInTailPosition(arity.body)
|
|
274
|
+
// Try to compile this arity
|
|
275
|
+
// store the compiled body if successful
|
|
276
|
+
// wrap the body in a do form so the compiler can handle it
|
|
277
|
+
// at the top level dispatcher
|
|
278
|
+
const compiled = compile(
|
|
279
|
+
v.list([v.symbol(specialFormKeywords.do), ...arity.body])
|
|
280
|
+
)
|
|
281
|
+
if (compiled !== null) {
|
|
282
|
+
arity.compiledBody = compiled
|
|
283
|
+
}
|
|
304
284
|
}
|
|
305
285
|
const fn = v.multiArityFunction(arities, env)
|
|
306
286
|
if (fnName) {
|
package/src/core/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type { Runtime, RuntimeSnapshot, RuntimeOptions } from './runtime'
|
|
|
12
12
|
|
|
13
13
|
// Module system
|
|
14
14
|
export { resolveModuleOrder } from './module'
|
|
15
|
-
export { makeCoreModule } from './core
|
|
15
|
+
export { makeCoreModule } from './modules/core'
|
|
16
16
|
export type {
|
|
17
17
|
RuntimeModule,
|
|
18
18
|
NamespaceDeclaration,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const specialFormKeywords = {
|
|
2
|
+
// Core forms
|
|
3
|
+
def: 'def',
|
|
4
|
+
do: 'do',
|
|
5
|
+
fn: 'fn',
|
|
6
|
+
if: 'if',
|
|
7
|
+
let: 'let',
|
|
8
|
+
loop: 'loop',
|
|
9
|
+
recur: 'recur',
|
|
10
|
+
quote: 'quote',
|
|
11
|
+
try: 'try',
|
|
12
|
+
var: 'var',
|
|
13
|
+
// Namespace form
|
|
14
|
+
ns: 'ns',
|
|
15
|
+
// Macro forms
|
|
16
|
+
defmacro: 'defmacro',
|
|
17
|
+
quasiquote: 'quasiquote',
|
|
18
|
+
// Multi methods
|
|
19
|
+
defmulti: 'defmulti',
|
|
20
|
+
defmethod: 'defmethod',
|
|
21
|
+
// Binding forms
|
|
22
|
+
binding: 'binding',
|
|
23
|
+
'set!': 'set!',
|
|
24
|
+
letfn: 'letfn',
|
|
25
|
+
// Lazy forms
|
|
26
|
+
delay: 'delay',
|
|
27
|
+
'lazy-seq': 'lazy-seq',
|
|
28
|
+
async: 'async',
|
|
29
|
+
// JS INTEROP
|
|
30
|
+
'.': '.',
|
|
31
|
+
'js/new': 'js/new',
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
export const valueKeywords = {
|
|
35
|
+
// Core values
|
|
36
|
+
boolean: 'boolean',
|
|
37
|
+
function: 'function',
|
|
38
|
+
nativeFunction: 'native-function',
|
|
39
|
+
keyword: 'keyword',
|
|
40
|
+
list: 'list',
|
|
41
|
+
macro: 'macro',
|
|
42
|
+
map: 'map',
|
|
43
|
+
nil: 'nil',
|
|
44
|
+
number: 'number',
|
|
45
|
+
regex: 'regex',
|
|
46
|
+
set: 'set',
|
|
47
|
+
string: 'string',
|
|
48
|
+
symbol: 'symbol',
|
|
49
|
+
vector: 'vector',
|
|
50
|
+
// Stateful values
|
|
51
|
+
atom: 'atom',
|
|
52
|
+
delay: 'delay',
|
|
53
|
+
multiMethod: 'multi-method',
|
|
54
|
+
volatile: 'volatile',
|
|
55
|
+
var: 'var',
|
|
56
|
+
// Exotic values
|
|
57
|
+
cons: 'cons',
|
|
58
|
+
lazySeq: 'lazy-seq',
|
|
59
|
+
reduced: 'reduced',
|
|
60
|
+
// Async value
|
|
61
|
+
pending: 'pending',
|
|
62
|
+
// Namespace representation
|
|
63
|
+
namespace: 'namespace',
|
|
64
|
+
// Boxed JS values, Interop containers
|
|
65
|
+
jsValue: 'js-value',
|
|
66
|
+
} as const
|
|
67
|
+
/** Tokens */
|
|
68
|
+
export const tokenKeywords = {
|
|
69
|
+
LParen: 'LParen',
|
|
70
|
+
RParen: 'RParen',
|
|
71
|
+
LBracket: 'LBracket',
|
|
72
|
+
RBracket: 'RBracket',
|
|
73
|
+
LBrace: 'LBrace',
|
|
74
|
+
RBrace: 'RBrace',
|
|
75
|
+
String: 'String',
|
|
76
|
+
Number: 'Number',
|
|
77
|
+
Keyword: 'Keyword',
|
|
78
|
+
Quote: 'Quote',
|
|
79
|
+
Quasiquote: 'Quasiquote',
|
|
80
|
+
Unquote: 'Unquote',
|
|
81
|
+
UnquoteSplicing: 'UnquoteSplicing',
|
|
82
|
+
Comment: 'Comment',
|
|
83
|
+
Whitespace: 'Whitespace',
|
|
84
|
+
Symbol: 'Symbol',
|
|
85
|
+
AnonFnStart: 'AnonFnStart',
|
|
86
|
+
Deref: 'Deref',
|
|
87
|
+
Regex: 'Regex',
|
|
88
|
+
VarQuote: 'VarQuote',
|
|
89
|
+
Meta: 'Meta',
|
|
90
|
+
SetStart: 'SetStart',
|
|
91
|
+
} as const
|
|
92
|
+
export const tokenSymbols = {
|
|
93
|
+
Quote: 'quote',
|
|
94
|
+
Quasiquote: 'quasiquote',
|
|
95
|
+
Unquote: 'unquote',
|
|
96
|
+
UnquoteSplicing: 'unquote-splicing',
|
|
97
|
+
LParen: '(',
|
|
98
|
+
RParen: ')',
|
|
99
|
+
LBracket: '[',
|
|
100
|
+
RBracket: ']',
|
|
101
|
+
LBrace: '{',
|
|
102
|
+
RBrace: '}',
|
|
103
|
+
} as const
|
|
104
|
+
export type TokenSymbols = (typeof tokenSymbols)[keyof typeof tokenSymbols]
|
|
105
|
+
export type TokenKinds = keyof typeof tokenKeywords
|