fez-lisp 1.0.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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/cli/index.js +162 -0
- package/index.js +8 -0
- package/js.svg +14 -0
- package/lib/baked/std.js +1 -0
- package/lib/src/std.lisp +275 -0
- package/logo.svg +8 -0
- package/package.json +33 -0
- package/src/compiler.js +361 -0
- package/src/enums.js +79 -0
- package/src/interpreter.js +29 -0
- package/src/parser.js +45 -0
- package/src/tokeniser.js +1036 -0
- package/src/utils.js +258 -0
package/src/utils.js
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
import std from '../lib/baked/std.js'
|
2
|
+
import { comp } from './compiler.js'
|
3
|
+
import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './enums.js'
|
4
|
+
import { evaluate, run } from './interpreter.js'
|
5
|
+
import { parse } from './parser.js'
|
6
|
+
export const logError = (error) => console.log('\x1b[31m', error, '\x1b[0m')
|
7
|
+
export const logSuccess = (output) => console.log(output, '\x1b[0m')
|
8
|
+
export const removeNoCode = (source) =>
|
9
|
+
source
|
10
|
+
.replace(/;.+/g, '')
|
11
|
+
.replace(/[\s\s]+(?=[^"]*(?:"[^"]*"[^"]*)*$)/g, ' ')
|
12
|
+
.trim()
|
13
|
+
export const isBalancedParenthesis = (sourceCode) => {
|
14
|
+
let count = 0
|
15
|
+
const stack = []
|
16
|
+
const str = sourceCode.match(/[/\(|\)](?=[^"]*(?:"[^"]*"[^"]*)*$)/g) ?? []
|
17
|
+
const pairs = { ')': '(' }
|
18
|
+
for (let i = 0; i < str.length; ++i)
|
19
|
+
if (str[i] === '(') stack.push(str[i])
|
20
|
+
else if (str[i] in pairs) if (stack.pop() !== pairs[str[i]]) ++count
|
21
|
+
return count - stack.length
|
22
|
+
}
|
23
|
+
export const escape = (char) => {
|
24
|
+
switch (char) {
|
25
|
+
case '\\':
|
26
|
+
return '\\'
|
27
|
+
case 'n':
|
28
|
+
return '\n'
|
29
|
+
case 'r':
|
30
|
+
return '\r'
|
31
|
+
case 't':
|
32
|
+
return '\t'
|
33
|
+
case 's':
|
34
|
+
return ' '
|
35
|
+
case '"':
|
36
|
+
return '"'
|
37
|
+
default:
|
38
|
+
return ''
|
39
|
+
}
|
40
|
+
}
|
41
|
+
export const stringifyType = (type) =>
|
42
|
+
Array.isArray(type)
|
43
|
+
? `(array ${type.map((t) => stringifyType(t)).join(' ')})`
|
44
|
+
: typeof type
|
45
|
+
export const lispify = (result) =>
|
46
|
+
typeof result === 'function'
|
47
|
+
? `(λ)`
|
48
|
+
: Array.isArray(result)
|
49
|
+
? JSON.stringify(result, (_, value) => {
|
50
|
+
switch (typeof value) {
|
51
|
+
case 'number':
|
52
|
+
return Number(value)
|
53
|
+
case 'function':
|
54
|
+
return 'λ'
|
55
|
+
case 'undefined':
|
56
|
+
case 'symbol':
|
57
|
+
return 0
|
58
|
+
case 'boolean':
|
59
|
+
return +value
|
60
|
+
default:
|
61
|
+
return value
|
62
|
+
}
|
63
|
+
})
|
64
|
+
.replace(new RegExp(/\[/g), `(Array `)
|
65
|
+
.replace(new RegExp(/\]/g), ')')
|
66
|
+
.replace(new RegExp(/\,/g), ' ')
|
67
|
+
.replace(new RegExp(/"λ"/g), 'λ')
|
68
|
+
: typeof result === 'string'
|
69
|
+
? `"${result}"`
|
70
|
+
: result == undefined
|
71
|
+
? '(void)'
|
72
|
+
: result
|
73
|
+
export const stringifyArgs = (args) =>
|
74
|
+
args
|
75
|
+
.map((x) =>
|
76
|
+
Array.isArray(x)
|
77
|
+
? `(${stringifyArgs(x)})`
|
78
|
+
: x[TYPE] === APPLY || x[TYPE] === WORD
|
79
|
+
? x[VALUE]
|
80
|
+
: JSON.stringify(x[VALUE])
|
81
|
+
.replace(new RegExp(/\[/g), '(')
|
82
|
+
.replace(new RegExp(/\]/g), ')')
|
83
|
+
.replace(new RegExp(/\,/g), ' ')
|
84
|
+
.replace(new RegExp(/"/g), '')
|
85
|
+
)
|
86
|
+
.join(' ')
|
87
|
+
export const isForbiddenVariableName = (name) => {
|
88
|
+
switch (name) {
|
89
|
+
case '_':
|
90
|
+
case KEYWORDS.CAST_TYPE:
|
91
|
+
case KEYWORDS.DEFINE_VARIABLE:
|
92
|
+
// case KEYWORDS.DESTRUCTURING_ASSIGMENT:
|
93
|
+
return true
|
94
|
+
default:
|
95
|
+
return false
|
96
|
+
}
|
97
|
+
}
|
98
|
+
export const isAtom = (arg, env) => {
|
99
|
+
if (arg[TYPE] === ATOM) return 1
|
100
|
+
else {
|
101
|
+
const atom = evaluate(arg, env)
|
102
|
+
return +(typeof atom === 'number' || typeof atom === 'string')
|
103
|
+
}
|
104
|
+
}
|
105
|
+
export const isEqual = (a, b) =>
|
106
|
+
+(
|
107
|
+
(Array.isArray(a) &&
|
108
|
+
a.length === b.length &&
|
109
|
+
!a.some((_, i) => !isEqual(a.at(i), b.at(i)))) ||
|
110
|
+
a === b ||
|
111
|
+
0
|
112
|
+
)
|
113
|
+
export const isEqualTypes = (a, b) =>
|
114
|
+
(typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
|
115
|
+
(Array.isArray(a) &&
|
116
|
+
Array.isArray(b) &&
|
117
|
+
(!a.length ||
|
118
|
+
!b.length ||
|
119
|
+
!(a.length > b.length ? a : b).some(
|
120
|
+
(_, i, bigger) =>
|
121
|
+
!isEqualTypes(
|
122
|
+
bigger.at(i),
|
123
|
+
(a.length > b.length ? b : a).at(
|
124
|
+
i % (a.length > b.length ? b : a).length
|
125
|
+
)
|
126
|
+
)
|
127
|
+
))) ||
|
128
|
+
false
|
129
|
+
export const isPartialTypes = (a, b) =>
|
130
|
+
(typeof a !== 'object' && typeof b !== 'object' && typeof a === typeof b) ||
|
131
|
+
(Array.isArray(a) &&
|
132
|
+
Array.isArray(b) &&
|
133
|
+
(!a.length ||
|
134
|
+
!b.length ||
|
135
|
+
!(a.length < b.length ? a : b).some(
|
136
|
+
(_, i, smaller) =>
|
137
|
+
!isEqualTypes(
|
138
|
+
smaller.at(i),
|
139
|
+
(a.length < b.length ? b : a).at(
|
140
|
+
i % (a.length < b.length ? b : a).length
|
141
|
+
)
|
142
|
+
)
|
143
|
+
))) ||
|
144
|
+
false
|
145
|
+
export const handleUnbalancedParens = (source) => {
|
146
|
+
const diff = isBalancedParenthesis(removeNoCode(source))
|
147
|
+
if (diff !== 0)
|
148
|
+
throw new SyntaxError(
|
149
|
+
`Parenthesis are unbalanced by ${diff > 0 ? '+' : ''}${diff}`
|
150
|
+
)
|
151
|
+
return source
|
152
|
+
}
|
153
|
+
export const handleUnbalancedQuotes = (source) => {
|
154
|
+
const diff = (source.match(/\"/g) ?? []).length % 2
|
155
|
+
if (diff !== 0) throw new SyntaxError(`Quotes are unbalanced "`)
|
156
|
+
return source
|
157
|
+
}
|
158
|
+
export const treeShake = (ast, libs) => {
|
159
|
+
const deps = libs.reduce((a, x) => a.add(x.at(1)[VALUE]), new Set())
|
160
|
+
const visited = new Map()
|
161
|
+
const dfs = (tree) =>
|
162
|
+
Array.isArray(tree)
|
163
|
+
? tree.forEach((a) => dfs(a))
|
164
|
+
: (tree[TYPE] === APPLY || tree[TYPE] === WORD) &&
|
165
|
+
deps.has(tree[VALUE]) &&
|
166
|
+
visited.set(tree[VALUE], tree[VALUE])
|
167
|
+
dfs(ast)
|
168
|
+
dfs(libs.filter((x) => visited.has(x.at(1)[VALUE])).map((x) => x.at(-1)))
|
169
|
+
return libs.filter((x) => visited.has(x.at(1)[VALUE]))
|
170
|
+
}
|
171
|
+
export const runFromCompiled = (source) => {
|
172
|
+
const tree = parse(
|
173
|
+
handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
|
174
|
+
)
|
175
|
+
if (Array.isArray(tree)) {
|
176
|
+
const compiled = comp(tree)
|
177
|
+
const JavaScript = `${compiled.top}${compiled.program}`
|
178
|
+
return eval(JavaScript)
|
179
|
+
}
|
180
|
+
}
|
181
|
+
export const runFromInterpreted = (source, env = {}) => {
|
182
|
+
const tree = parse(
|
183
|
+
handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
|
184
|
+
)
|
185
|
+
if (Array.isArray(tree)) return run(tree, env)
|
186
|
+
}
|
187
|
+
export const dfs = (tree, callback) => {
|
188
|
+
if (Array.isArray(tree)) for (const branch of tree) dfs(branch)
|
189
|
+
else callback(tree)
|
190
|
+
}
|
191
|
+
export const deepClone = (ast) => JSON.parse(JSON.stringify(ast))
|
192
|
+
export const fez = (source, options = {}) => {
|
193
|
+
const env = options.env ?? {}
|
194
|
+
try {
|
195
|
+
let code
|
196
|
+
if (options.errors)
|
197
|
+
code = handleUnbalancedQuotes(
|
198
|
+
handleUnbalancedParens(removeNoCode(source))
|
199
|
+
)
|
200
|
+
else code = removeNoCode(source)
|
201
|
+
const parsed = parse(code)
|
202
|
+
const standard = options.std
|
203
|
+
? options.shake
|
204
|
+
? treeShake(parsed, std)
|
205
|
+
: std
|
206
|
+
: []
|
207
|
+
const ast = [...standard, ...parsed]
|
208
|
+
if (options.compile) return Object.values(comp(deepClone(ast))).join('')
|
209
|
+
return run(ast, env)
|
210
|
+
} catch (error) {
|
211
|
+
if (options.errors) {
|
212
|
+
logError(error.message)
|
213
|
+
}
|
214
|
+
return error.message
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
export const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
219
|
+
export const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
|
220
|
+
export const colonNamesTo$ = (name) => name.replace(new RegExp(/\:/g), '$')
|
221
|
+
export const commaToLodash = (name) => name.replace(new RegExp(/\,/g), '_')
|
222
|
+
export const arrowToTo = (name) => name.replace(new RegExp(/->/g), '-to-')
|
223
|
+
export const questionMarkToLodash = (name) =>
|
224
|
+
name.replace(new RegExp(/\?/g), 'Predicate')
|
225
|
+
export const exclamationMarkMarkToLodash = (name) =>
|
226
|
+
name.replace(new RegExp(/\!/g), 'Effect')
|
227
|
+
export const toCamelCase = (name) => {
|
228
|
+
let out = name[0]
|
229
|
+
for (let i = 1; i < name.length; ++i) {
|
230
|
+
const current = name[i],
|
231
|
+
prev = name[i - 1]
|
232
|
+
if (current === '-') continue
|
233
|
+
else if (prev === '-') out += current.toUpperCase()
|
234
|
+
else out += current
|
235
|
+
}
|
236
|
+
return out
|
237
|
+
}
|
238
|
+
export const deepRename = (name, newName, tree) => {
|
239
|
+
if (Array.isArray(tree))
|
240
|
+
for (const branch of tree) {
|
241
|
+
// Figure out a non mutable solution so
|
242
|
+
// I can get rid of deep copy
|
243
|
+
if (branch[VALUE] === name) branch[VALUE] = `()=>${newName}`
|
244
|
+
deepRename(name, newName, branch)
|
245
|
+
}
|
246
|
+
}
|
247
|
+
export const lispToJavaScriptVariableName = (name) =>
|
248
|
+
toCamelCase(
|
249
|
+
arrowToTo(
|
250
|
+
dotNamesToEmpty(
|
251
|
+
colonNamesTo$(
|
252
|
+
exclamationMarkMarkToLodash(
|
253
|
+
questionMarkToLodash(commaToLodash(earMuffsToLodashes(name)))
|
254
|
+
)
|
255
|
+
)
|
256
|
+
)
|
257
|
+
)
|
258
|
+
)
|