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/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
+ )