fez-lisp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ )