conjure-js 0.0.1

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.
Files changed (80) hide show
  1. package/conjure +0 -0
  2. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  3. package/dist/assets/editor.worker-CdQrwHl8.js +26 -0
  4. package/dist/assets/main-A7ZMId9A.css +1 -0
  5. package/dist/assets/main-CmI-7epE.js +3137 -0
  6. package/dist/index.html +195 -0
  7. package/dist/vite.svg +1 -0
  8. package/package.json +68 -0
  9. package/src/bin/__fixtures__/smoke/app/lib.clj +4 -0
  10. package/src/bin/__fixtures__/smoke/app/main.clj +4 -0
  11. package/src/bin/__fixtures__/smoke/repl-smoke.ts +12 -0
  12. package/src/bin/bencode.ts +205 -0
  13. package/src/bin/cli.ts +250 -0
  14. package/src/bin/nrepl-utils.ts +59 -0
  15. package/src/bin/nrepl.ts +393 -0
  16. package/src/bin/version.ts +4 -0
  17. package/src/clojure/core.clj +620 -0
  18. package/src/clojure/core.clj.d.ts +189 -0
  19. package/src/clojure/demo/math.clj +16 -0
  20. package/src/clojure/demo/math.clj.d.ts +4 -0
  21. package/src/clojure/demo.clj +42 -0
  22. package/src/clojure/demo.clj.d.ts +0 -0
  23. package/src/clojure/generated/builtin-namespace-registry.ts +14 -0
  24. package/src/clojure/generated/clojure-core-source.ts +623 -0
  25. package/src/clojure/generated/clojure-string-source.ts +196 -0
  26. package/src/clojure/string.clj +192 -0
  27. package/src/clojure/string.clj.d.ts +25 -0
  28. package/src/core/assertions.ts +134 -0
  29. package/src/core/conversions.ts +108 -0
  30. package/src/core/core-env.ts +58 -0
  31. package/src/core/env.ts +78 -0
  32. package/src/core/errors.ts +39 -0
  33. package/src/core/evaluator/apply.ts +114 -0
  34. package/src/core/evaluator/arity.ts +174 -0
  35. package/src/core/evaluator/collections.ts +25 -0
  36. package/src/core/evaluator/destructure.ts +247 -0
  37. package/src/core/evaluator/dispatch.ts +73 -0
  38. package/src/core/evaluator/evaluate.ts +100 -0
  39. package/src/core/evaluator/expand.ts +79 -0
  40. package/src/core/evaluator/index.ts +72 -0
  41. package/src/core/evaluator/quasiquote.ts +87 -0
  42. package/src/core/evaluator/recur-check.ts +109 -0
  43. package/src/core/evaluator/special-forms.ts +517 -0
  44. package/src/core/factories.ts +155 -0
  45. package/src/core/gensym.ts +9 -0
  46. package/src/core/index.ts +76 -0
  47. package/src/core/positions.ts +38 -0
  48. package/src/core/printer.ts +86 -0
  49. package/src/core/reader.ts +559 -0
  50. package/src/core/scanners.ts +93 -0
  51. package/src/core/session.ts +610 -0
  52. package/src/core/stdlib/arithmetic.ts +361 -0
  53. package/src/core/stdlib/atoms.ts +88 -0
  54. package/src/core/stdlib/collections.ts +784 -0
  55. package/src/core/stdlib/errors.ts +81 -0
  56. package/src/core/stdlib/hof.ts +307 -0
  57. package/src/core/stdlib/meta.ts +48 -0
  58. package/src/core/stdlib/predicates.ts +240 -0
  59. package/src/core/stdlib/regex.ts +238 -0
  60. package/src/core/stdlib/strings.ts +311 -0
  61. package/src/core/stdlib/transducers.ts +256 -0
  62. package/src/core/stdlib/utils.ts +287 -0
  63. package/src/core/tokenizer.ts +437 -0
  64. package/src/core/transformations.ts +75 -0
  65. package/src/core/types.ts +258 -0
  66. package/src/main.ts +1 -0
  67. package/src/monaco-esm.d.ts +7 -0
  68. package/src/playground/clojure-tokens.ts +67 -0
  69. package/src/playground/editor.worker.ts +5 -0
  70. package/src/playground/find-form.ts +138 -0
  71. package/src/playground/playground.ts +342 -0
  72. package/src/playground/samples/00-welcome.clj +385 -0
  73. package/src/playground/samples/01-collections.clj +191 -0
  74. package/src/playground/samples/02-higher-order-functions.clj +215 -0
  75. package/src/playground/samples/03-destructuring.clj +194 -0
  76. package/src/playground/samples/04-strings-and-regex.clj +202 -0
  77. package/src/playground/samples/05-error-handling.clj +212 -0
  78. package/src/repl/repl.ts +116 -0
  79. package/tsconfig.build.json +10 -0
  80. package/tsconfig.json +31 -0
@@ -0,0 +1,258 @@
1
+ export const valueKeywords = {
2
+ number: 'number',
3
+ string: 'string',
4
+ boolean: 'boolean',
5
+ keyword: 'keyword',
6
+ nil: 'nil',
7
+ symbol: 'symbol',
8
+ list: 'list',
9
+ vector: 'vector',
10
+ map: 'map',
11
+ function: 'function',
12
+ nativeFunction: 'native-function',
13
+ macro: 'macro',
14
+ multiMethod: 'multi-method',
15
+ atom: 'atom',
16
+ reduced: 'reduced',
17
+ volatile: 'volatile',
18
+ regex: 'regex',
19
+ } as const
20
+ export type ValueKeywords = (typeof valueKeywords)[keyof typeof valueKeywords]
21
+
22
+ export type CljNumber = { kind: 'number'; value: number }
23
+ export type CljString = { kind: 'string'; value: string }
24
+ export type CljBoolean = { kind: 'boolean'; value: boolean }
25
+ export type CljKeyword = { kind: 'keyword'; name: string }
26
+ export type CljNil = { kind: 'nil'; value: null }
27
+ export type CljSymbol = { kind: 'symbol'; name: string }
28
+ export type CljList = { kind: 'list'; value: CljValue[] }
29
+ export type CljVector = { kind: 'vector'; value: CljValue[] }
30
+ export type CljMap = { kind: 'map'; entries: [CljValue, CljValue][] }
31
+ export type Env = {
32
+ bindings: Map<string, CljValue>
33
+ outer: Env | null
34
+ namespace?: string // only present on namespace-root envs
35
+ aliases?: Map<string, Env> // only present on namespace-root envs; set by :as
36
+ readerAliases?: Map<string, string> // only present on namespace-root envs; set by :as-alias
37
+ resolveNs?: (name: string) => Env | null // only present on the root coreEnv
38
+ }
39
+
40
+ export type DestructurePattern = CljSymbol | CljVector | CljMap
41
+
42
+ export type Arity = {
43
+ params: DestructurePattern[]
44
+ restParam: DestructurePattern | null
45
+ body: CljValue[]
46
+ }
47
+
48
+ export type CljFunction = {
49
+ kind: 'function'
50
+ arities: Arity[]
51
+ env: Env
52
+ meta?: CljMap
53
+ }
54
+
55
+ export type CljMacro = {
56
+ kind: 'macro'
57
+ arities: Arity[]
58
+ env: Env
59
+ }
60
+
61
+ export type CljAtom = { kind: 'atom'; value: CljValue }
62
+ export type CljReduced = { kind: 'reduced'; value: CljValue }
63
+ export type CljVolatile = { kind: 'volatile'; value: CljValue }
64
+ export type CljRegex = { kind: 'regex'; pattern: string; flags: string }
65
+
66
+ export type CljMultiMethod = {
67
+ kind: 'multi-method'
68
+ name: string
69
+ dispatchFn: CljFunction | CljNativeFunction
70
+ methods: Array<{ dispatchVal: CljValue; fn: CljFunction | CljNativeFunction }>
71
+ defaultMethod?: CljFunction | CljNativeFunction
72
+ }
73
+
74
+ export type EvaluationContext = {
75
+ evaluate: (expr: CljValue, env: Env) => CljValue
76
+ evaluateForms: (forms: CljValue[], env: Env) => CljValue
77
+ applyFunction: (
78
+ fn: CljFunction | CljNativeFunction,
79
+ args: CljValue[],
80
+ callEnv: Env
81
+ ) => CljValue
82
+ /** Invokes any IFn value: functions, native functions, keywords, and maps. */
83
+ applyCallable: (fn: CljValue, args: CljValue[], callEnv: Env) => CljValue
84
+ applyMacro: (macro: CljMacro, rawArgs: CljValue[]) => CljValue
85
+ expandAll: (form: CljValue, env: Env) => CljValue
86
+ }
87
+
88
+ export type CljNativeFunction = {
89
+ kind: 'native-function'
90
+ name: string
91
+ fn: (...args: CljValue[]) => CljValue
92
+ // Only used in case the function needs to access the evaluation context
93
+ fnWithContext?: (
94
+ ctx: EvaluationContext,
95
+ callEnv: Env,
96
+ ...args: CljValue[]
97
+ ) => CljValue
98
+ meta?: CljMap
99
+ }
100
+
101
+ export type CljValue =
102
+ | CljNumber
103
+ | CljString
104
+ | CljBoolean
105
+ | CljKeyword
106
+ | CljNil
107
+ | CljSymbol
108
+ | CljList
109
+ | CljVector
110
+ | CljMap
111
+ | CljFunction
112
+ | CljNativeFunction
113
+ | CljMacro
114
+ | CljMultiMethod
115
+ | CljAtom
116
+ | CljReduced
117
+ | CljVolatile
118
+ | CljRegex
119
+
120
+ /** Tokens */
121
+ export const tokenKeywords = {
122
+ LParen: 'LParen',
123
+ RParen: 'RParen',
124
+ LBracket: 'LBracket',
125
+ RBracket: 'RBracket',
126
+ LBrace: 'LBrace',
127
+ RBrace: 'RBrace',
128
+ String: 'String',
129
+ Number: 'Number',
130
+ Keyword: 'Keyword',
131
+ Quote: 'Quote',
132
+ Quasiquote: 'Quasiquote',
133
+ Unquote: 'Unquote',
134
+ UnquoteSplicing: 'UnquoteSplicing',
135
+ Comment: 'Comment',
136
+ Whitespace: 'Whitespace',
137
+ Symbol: 'Symbol',
138
+ AnonFnStart: 'AnonFnStart',
139
+ Deref: 'Deref',
140
+ Regex: 'Regex',
141
+ } as const
142
+ export const tokenSymbols = {
143
+ Quote: 'quote',
144
+ Quasiquote: 'quasiquote',
145
+ Unquote: 'unquote',
146
+ UnquoteSplicing: 'unquote-splicing',
147
+ LParen: '(',
148
+ RParen: ')',
149
+ LBracket: '[',
150
+ RBracket: ']',
151
+ LBrace: '{',
152
+ RBrace: '}',
153
+ } as const
154
+ export type TokenSymbols = (typeof tokenSymbols)[keyof typeof tokenSymbols]
155
+ export type TokenKinds = keyof typeof tokenKeywords
156
+
157
+ export type Cursor = {
158
+ line: number
159
+ col: number
160
+ offset: number
161
+ }
162
+
163
+ export type Pos = { start: number; end: number } // absolute char offsets into the source string
164
+
165
+ export type TokenLParen = {
166
+ kind: 'LParen'
167
+ value: '('
168
+ }
169
+ export type TokenRParen = {
170
+ kind: 'RParen'
171
+ value: ')'
172
+ }
173
+ export type TokenLBracket = {
174
+ kind: 'LBracket'
175
+ value: '['
176
+ }
177
+ export type TokenRBracket = {
178
+ kind: 'RBracket'
179
+ value: ']'
180
+ }
181
+ export type TokenLBrace = {
182
+ kind: 'LBrace'
183
+ value: '{'
184
+ }
185
+ export type TokenRBrace = {
186
+ kind: 'RBrace'
187
+ value: '}'
188
+ }
189
+ export type TokenString = {
190
+ kind: 'String'
191
+ value: string
192
+ }
193
+ export type TokenNumber = {
194
+ kind: 'Number'
195
+ value: number
196
+ }
197
+ export type TokenKeyword = {
198
+ kind: 'Keyword'
199
+ value: string
200
+ }
201
+ export type TokenQuote = {
202
+ kind: 'Quote'
203
+ value: 'quote'
204
+ }
205
+ export type TokenComment = {
206
+ kind: 'Comment'
207
+ value: string
208
+ }
209
+ export type TokenWhitespace = {
210
+ kind: 'Whitespace'
211
+ }
212
+ export type TokenSymbol = {
213
+ kind: 'Symbol'
214
+ value: string
215
+ }
216
+ export type TokenQuasiquote = {
217
+ kind: 'Quasiquote'
218
+ value: 'quasiquote'
219
+ }
220
+ export type TokenUnquote = {
221
+ kind: 'Unquote'
222
+ value: 'unquote'
223
+ }
224
+ export type TokenUnquoteSplicing = {
225
+ kind: 'UnquoteSplicing'
226
+ value: 'unquote-splicing'
227
+ }
228
+ export type TokenAnonFnStart = {
229
+ kind: 'AnonFnStart'
230
+ }
231
+ export type TokenDeref = {
232
+ kind: 'Deref'
233
+ }
234
+ export type TokenRegex = {
235
+ kind: 'Regex'
236
+ value: string
237
+ }
238
+ export type Token = (
239
+ | TokenLParen
240
+ | TokenRParen
241
+ | TokenLBracket
242
+ | TokenRBracket
243
+ | TokenLBrace
244
+ | TokenRBrace
245
+ | TokenString
246
+ | TokenNumber
247
+ | TokenKeyword
248
+ | TokenQuote
249
+ | TokenComment
250
+ | TokenWhitespace
251
+ | TokenSymbol
252
+ | TokenQuasiquote
253
+ | TokenUnquote
254
+ | TokenUnquoteSplicing
255
+ | TokenAnonFnStart
256
+ | TokenDeref
257
+ | TokenRegex
258
+ ) & { start: Cursor; end: Cursor }
package/src/main.ts ADDED
@@ -0,0 +1 @@
1
+ import './playground/playground'
@@ -0,0 +1,7 @@
1
+ declare module 'monaco-editor/esm/vs/basic-languages/clojure/clojure' {
2
+ import type * as Monaco from 'monaco-editor'
3
+
4
+ export const language: Monaco.languages.IMonarchLanguage
5
+ }
6
+
7
+ declare module 'monaco-editor/esm/vs/editor/editor.worker'
@@ -0,0 +1,67 @@
1
+ import { language as clojureLang } from 'monaco-editor/esm/vs/basic-languages/clojure/clojure'
2
+ import type * as Monaco from 'monaco-editor'
3
+
4
+ export const THEME_ID = 'vscode-clj-dark'
5
+
6
+ /**
7
+ * Keep Monaco's native Clojure highlighting, but stop treating `(comment ...)`
8
+ * as a real comment block. In Clojure that form is a macro and its contents
9
+ * should still be tokenized as normal code.
10
+ */
11
+ export function registerClojureLanguage(monaco: typeof Monaco): void {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const base = clojureLang as any
14
+
15
+ const filteredWhitespace = base.tokenizer.whitespace.filter(
16
+ // Monaco's built-in Clojure grammar marks `(comment ... )` as comment.
17
+ // We keep `;` comments, but drop this macro-specific rule.
18
+ (rule: unknown) =>
19
+ !(
20
+ Array.isArray(rule) &&
21
+ rule[0] instanceof RegExp &&
22
+ rule[1] === 'comment' &&
23
+ rule[0].source === '\\(comment\\b'
24
+ ),
25
+ )
26
+
27
+ monaco.languages.setMonarchTokensProvider('clojure', {
28
+ ...base,
29
+ tokenizer: {
30
+ ...base.tokenizer,
31
+ whitespace: filteredWhitespace,
32
+ },
33
+ })
34
+ }
35
+
36
+ /**
37
+ * Small VSCode-like theme override:
38
+ * - keep Monaco's default tokenization behavior
39
+ * - use neutral dark background/colors similar to VS Code Dark+
40
+ */
41
+ export function defineMonacoTheme(monaco: typeof Monaco): void {
42
+ monaco.editor.defineTheme(THEME_ID, {
43
+ base: 'vs-dark',
44
+ inherit: true,
45
+ rules: [
46
+ { token: 'comment.clj', foreground: '6a9955' },
47
+ { token: 'string.clj', foreground: 'ce9178' },
48
+ { token: 'number.clj', foreground: 'b5cea8' },
49
+ { token: 'keyword.clj', foreground: 'c586c0' },
50
+ { token: 'constant.clj', foreground: '4fc1ff' },
51
+ { token: 'meta.clj', foreground: 'd7ba7d' },
52
+ { token: 'identifier.clj', foreground: 'd4d4d4' },
53
+ ],
54
+ colors: {
55
+ 'editor.background': '#1e1e1e',
56
+ 'editor.foreground': '#d4d4d4',
57
+ 'editor.lineHighlightBackground': '#2a2d2e',
58
+ 'editorCursor.foreground': '#aeafad',
59
+ 'editor.selectionBackground': '#264f78',
60
+ 'editor.inactiveSelectionBackground': '#3a3d41',
61
+ 'editorLineNumber.foreground': '#858585',
62
+ 'editorLineNumber.activeForeground': '#c6c6c6',
63
+ 'editorBracketMatch.background': '#0064001a',
64
+ 'editorBracketMatch.border': '#888888',
65
+ },
66
+ })
67
+ }
@@ -0,0 +1,5 @@
1
+ // Thin wrapper so Vite can bundle Monaco's editor worker via the
2
+ // `new URL('./editor.worker.ts', import.meta.url)` pattern.
3
+ // Vite only recognises the worker-bundling pattern for relative paths;
4
+ // bare package specifiers inside new URL() are not resolved the same way.
5
+ import 'monaco-editor/esm/vs/editor/editor.worker'
@@ -0,0 +1,138 @@
1
+ import { tokenize } from '../core/tokenizer'
2
+
3
+ export type FormRange = {
4
+ /** Inclusive start offset in the source string */
5
+ start: number
6
+ /** Exclusive end offset in the source string */
7
+ end: number
8
+ }
9
+
10
+ // AnonFnStart (#() consumes both # and (, so it opens a RParen-closed scope
11
+ const OPEN = new Set(['LParen', 'LBracket', 'LBrace', 'AnonFnStart'])
12
+ const CLOSE = new Set(['RParen', 'RBracket', 'RBrace'])
13
+ const PREFIX = new Set(['Quote', 'Quasiquote', 'Unquote', 'UnquoteSplicing'])
14
+ const SKIP = new Set(['Whitespace', 'Comment'])
15
+
16
+ // ── Public API ────────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Calva-style "form before cursor" heuristic.
20
+ *
21
+ * Scans backwards from the cursor offset to find the last **complete** form:
22
+ *
23
+ * - If the last token before the cursor is a close bracket `)` `]` `}` →
24
+ * walk backwards to find its matching open bracket. The whole balanced
25
+ * expression is the form.
26
+ * - If the last token is an atom (symbol, number, keyword, string, …) →
27
+ * that atom is the form.
28
+ * - Reader-macro prefixes (`'` `` ` `` `~` `~@`) immediately before the form
29
+ * are included in the returned range.
30
+ *
31
+ * Returns null when the cursor is after an unmatched open bracket, in
32
+ * whitespace/comments with nothing before it, or if tokenization fails.
33
+ */
34
+ export function findFormBeforeCursor(
35
+ source: string,
36
+ cursorOffset: number,
37
+ ): FormRange | null {
38
+ let tokens
39
+ try {
40
+ tokens = tokenize(source)
41
+ } catch {
42
+ // Full-source tokenization failed (e.g. unsupported syntax elsewhere in
43
+ // the file). Retry with just the text up to the cursor — enough to find
44
+ // the form before it, and avoids any bad syntax that lives after it.
45
+ try {
46
+ tokens = tokenize(source.slice(0, cursorOffset))
47
+ } catch {
48
+ return null
49
+ }
50
+ }
51
+
52
+ // Relevant tokens: non-whitespace, non-comment, ending at or before cursor
53
+ const relevant = tokens.filter(
54
+ (t) => !SKIP.has(t.kind) && t.end.offset <= cursorOffset,
55
+ )
56
+
57
+ if (relevant.length === 0) return null
58
+
59
+ const lastIdx = relevant.length - 1
60
+ const last = relevant[lastIdx]
61
+
62
+ let range: FormRange
63
+
64
+ if (CLOSE.has(last.kind)) {
65
+ // Closing bracket — find its matching open
66
+ const openIdx = findMatchingOpen(relevant, lastIdx)
67
+ if (openIdx === -1) return null
68
+ range = {
69
+ start: relevant[openIdx].start.offset,
70
+ end: last.end.offset,
71
+ }
72
+ return extendWithPrefix(relevant, openIdx, range)
73
+ }
74
+
75
+ if (OPEN.has(last.kind)) {
76
+ // Cursor is right after an open bracket with no matching close before it.
77
+ // Return just the bracket itself so the evaluator reports an "unmatched
78
+ // bracket" error — correct and honest, no fallback to whole-buffer eval.
79
+ return { start: last.start.offset, end: last.end.offset }
80
+ }
81
+
82
+ if (!PREFIX.has(last.kind)) {
83
+ // Atom (symbol, number, keyword, string, nil, …)
84
+ range = { start: last.start.offset, end: last.end.offset }
85
+ return extendWithPrefix(relevant, lastIdx, range)
86
+ }
87
+
88
+ // Bare reader-macro prefix with nothing after it — nothing to eval.
89
+ return null
90
+ }
91
+
92
+ // ── Helpers ───────────────────────────────────────────────────────────────────
93
+
94
+ type Token = ReturnType<typeof tokenize>[number]
95
+
96
+ /** Scan backwards from closeIdx to find the index of the matching open bracket. */
97
+ function findMatchingOpen(relevant: Token[], closeIdx: number): number {
98
+ const closeKind = relevant[closeIdx].kind
99
+ const openKind =
100
+ closeKind === 'RParen'
101
+ ? 'LParen'
102
+ : closeKind === 'RBracket'
103
+ ? 'LBracket'
104
+ : 'LBrace'
105
+
106
+ let depth = 1
107
+ for (let i = closeIdx - 1; i >= 0; i--) {
108
+ const k = relevant[i].kind
109
+ if (k === closeKind) depth++
110
+ else if (
111
+ k === openKind ||
112
+ // AnonFnStart opens a RParen-closed scope just like LParen
113
+ (openKind === 'LParen' && k === 'AnonFnStart')
114
+ ) {
115
+ depth--
116
+ if (depth === 0) return i
117
+ }
118
+ }
119
+ return -1
120
+ }
121
+
122
+ /**
123
+ * If the token immediately before formStartIdx is a reader-macro prefix
124
+ * (`'`, `` ` ``, `~`, `~@`), extend the range to include it.
125
+ */
126
+ function extendWithPrefix(
127
+ relevant: Token[],
128
+ formStartIdx: number,
129
+ range: FormRange,
130
+ ): FormRange {
131
+ if (formStartIdx > 0) {
132
+ const prev = relevant[formStartIdx - 1]
133
+ if (PREFIX.has(prev.kind)) {
134
+ return { start: prev.start.offset, end: range.end }
135
+ }
136
+ }
137
+ return range
138
+ }