fez-lisp 1.2.62 → 1.3.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/lib/baked/std.js +1 -1
- package/package.json +1 -1
- package/src/compiler.js +350 -350
- package/src/interpreter.js +783 -743
- package/src/utils.js +832 -630
package/src/compiler.js
CHANGED
@@ -1,350 +1,350 @@
|
|
1
|
-
import {
|
2
|
-
APPLY,
|
3
|
-
ATOM,
|
4
|
-
PLACEHOLDER,
|
5
|
-
KEYWORDS,
|
6
|
-
TYPE,
|
7
|
-
VALUE,
|
8
|
-
WORD,
|
9
|
-
SUGGAR
|
10
|
-
} from './keywords.js'
|
11
|
-
import { leaf, isLeaf, AST } from './parser.js'
|
12
|
-
const deepRename = (name, newName, tree) => {
|
13
|
-
if (!isLeaf(tree))
|
14
|
-
for (const leaf of tree) {
|
15
|
-
// Figure out a non mutable solution so
|
16
|
-
// I can get rid of deep clone AST.parse(AST.stringify(ast))
|
17
|
-
if (leaf[VALUE] === name) leaf[VALUE] = newName
|
18
|
-
deepRename(name, newName, leaf)
|
19
|
-
}
|
20
|
-
}
|
21
|
-
const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
22
|
-
const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
|
23
|
-
const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
|
24
|
-
const moduleNameToLodashes = (name) => name.replace(new RegExp(/:/g), '_')
|
25
|
-
const questionMarkToPredicate = (name) =>
|
26
|
-
name.replace(new RegExp(/\?/g), '_predicate')
|
27
|
-
const exclamationMarkMarkToEffect = (name) =>
|
28
|
-
name.replace(new RegExp(/\!/g), '_effect')
|
29
|
-
const toCamelCase = (name) => {
|
30
|
-
let out = name[0]
|
31
|
-
for (let i = 1; i < name.length; ++i) {
|
32
|
-
const current = name[i],
|
33
|
-
prev = name[i - 1]
|
34
|
-
if (current === '-') continue
|
35
|
-
else if (prev === '-') out += current.toUpperCase()
|
36
|
-
else out += current
|
37
|
-
}
|
38
|
-
return out
|
39
|
-
}
|
40
|
-
const dashToLodashes = (name) => name.replace(new RegExp(/-/g), '_')
|
41
|
-
const keywordToHelper = (name) => {
|
42
|
-
switch (name) {
|
43
|
-
case KEYWORDS.ADDITION:
|
44
|
-
return '__add'
|
45
|
-
case KEYWORDS.MULTIPLICATION:
|
46
|
-
return '__mult'
|
47
|
-
case KEYWORDS.DIVISION:
|
48
|
-
return '__div'
|
49
|
-
case KEYWORDS.SUBTRACTION:
|
50
|
-
return '__sub'
|
51
|
-
case KEYWORDS.GREATHER_THAN:
|
52
|
-
return '__gt'
|
53
|
-
case KEYWORDS.EQUAL:
|
54
|
-
return '__eq'
|
55
|
-
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
56
|
-
return '__gteq'
|
57
|
-
case KEYWORDS.LESS_THAN:
|
58
|
-
return '__lt'
|
59
|
-
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
60
|
-
return '__lteq'
|
61
|
-
default:
|
62
|
-
return name
|
63
|
-
}
|
64
|
-
}
|
65
|
-
const lispToJavaScriptVariableName = (name) =>
|
66
|
-
dashToLodashes(
|
67
|
-
arrowFromTo(
|
68
|
-
dotNamesToEmpty(
|
69
|
-
exclamationMarkMarkToEffect(
|
70
|
-
questionMarkToPredicate(
|
71
|
-
moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
|
72
|
-
)
|
73
|
-
)
|
74
|
-
)
|
75
|
-
)
|
76
|
-
)
|
77
|
-
const Helpers = {
|
78
|
-
__add: `__add=(
|
79
|
-
__sub: `__sub=(
|
80
|
-
__mult: `__mult=(
|
81
|
-
__div: `__div=(
|
82
|
-
__gteq: '__gteq=(a,b)=>+(a>=b)',
|
83
|
-
__gt: '__gt=(a,b)=>+(a>b)',
|
84
|
-
__eq: '__eq=(a,b)=>+(a===b)',
|
85
|
-
__lteq: '__lteq=(a,b)=>+(a<=b)',
|
86
|
-
__lt: '__lt=(a,b)=>+(a<b)',
|
87
|
-
array: 'array=(...args)=>args',
|
88
|
-
not: 'not=(a)=>+!a',
|
89
|
-
and: `and=(...args)=>{
|
90
|
-
let circuit;
|
91
|
-
for(let i=0; i<args.length-1;++i){
|
92
|
-
circuit=args[i]
|
93
|
-
if(circuit) continue
|
94
|
-
else return 0
|
95
|
-
}
|
96
|
-
return args.at(-1) ? 1 : 0
|
97
|
-
}`,
|
98
|
-
or: `or=(...args)=>{
|
99
|
-
let circuit;
|
100
|
-
for(let i=0;i<args.length-1;++i) {
|
101
|
-
circuit = args[i]
|
102
|
-
if(circuit) return 1
|
103
|
-
else continue
|
104
|
-
}
|
105
|
-
return args.at(-1) ? 1 : 0
|
106
|
-
}`,
|
107
|
-
log_effect: `log_effect=(msg)=>{console.log(msg);return msg}`,
|
108
|
-
log_char_effect: `log_char_effect=(msg)=>{console.log(String.fromCharCode(msg));return msg}`,
|
109
|
-
log_string_effect: `log_string_effect=(msg)=>{console.log(msg.map(x=>String.fromCharCode(x)).join(''));return msg}`,
|
110
|
-
clear_effect: `clear_effect=()=>{console.clear();return 0}`,
|
111
|
-
get: 'get=(arr,i)=>arr.at(i)',
|
112
|
-
length: 'length=(arr)=>arr.length',
|
113
|
-
__tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
|
114
|
-
atom_predicate: `atom_predicate=(number)=>+(typeof number==='number')`,
|
115
|
-
lambda_predicate: `lambda_predicate=(fm)=>+(typeof fn==='function')`,
|
116
|
-
set_effect: `set_effect=function(array,index,value){if(arguments.length===1){array.pop()}else{array[index] = value};return array}`,
|
117
|
-
__error: `__error=(error)=>{throw new Error(error.map((x)=>String.fromCharCode(x)).join(''))}`
|
118
|
-
}
|
119
|
-
const semiColumnEdgeCases = new Set([
|
120
|
-
';)',
|
121
|
-
';-',
|
122
|
-
';+',
|
123
|
-
';*',
|
124
|
-
';%',
|
125
|
-
';&',
|
126
|
-
';/',
|
127
|
-
';:',
|
128
|
-
';.',
|
129
|
-
';=',
|
130
|
-
';<',
|
131
|
-
';>',
|
132
|
-
';|',
|
133
|
-
';,',
|
134
|
-
';?',
|
135
|
-
',,',
|
136
|
-
';;',
|
137
|
-
';]',
|
138
|
-
';^'
|
139
|
-
])
|
140
|
-
const parse = (Arguments, Drill) => Arguments.map((x) => compile(x, Drill))
|
141
|
-
const parseArgs = (Arguments, Drill, separator = ',') =>
|
142
|
-
parse(Arguments, Drill).join(separator)
|
143
|
-
const compile = (tree, Drill) => {
|
144
|
-
if (!tree) return ''
|
145
|
-
const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
|
146
|
-
if (first == undefined) return '[];'
|
147
|
-
const token = first[VALUE]
|
148
|
-
if (first[TYPE] === APPLY) {
|
149
|
-
switch (token) {
|
150
|
-
case KEYWORDS.BLOCK: {
|
151
|
-
if (Arguments.length > 1) {
|
152
|
-
return `(${Arguments.map((x) =>
|
153
|
-
(compile(x, Drill) ?? '').toString().trim()
|
154
|
-
)
|
155
|
-
.filter((x) => x !== undefined)
|
156
|
-
.join(',')});`
|
157
|
-
} else {
|
158
|
-
const res = compile(Arguments[0], Drill)
|
159
|
-
return res !== undefined ? res.toString().trim() : ''
|
160
|
-
}
|
161
|
-
}
|
162
|
-
case KEYWORDS.CALL_FUNCTION: {
|
163
|
-
const [first, ...rest] = Arguments
|
164
|
-
const apply = compile(first, Drill)
|
165
|
-
return `${
|
166
|
-
apply[apply.length - 1] === ';'
|
167
|
-
? apply.substring(0, apply.length - 1)
|
168
|
-
: apply
|
169
|
-
}(${parseArgs(rest, Drill)})`
|
170
|
-
}
|
171
|
-
case KEYWORDS.DEFINE_VARIABLE: {
|
172
|
-
const n = Arguments[0][VALUE]
|
173
|
-
const prefix = n.split(':')[0]
|
174
|
-
if (prefix === SUGGAR.RECURSION) {
|
175
|
-
const name = lispToJavaScriptVariableName(n)
|
176
|
-
const newName = `${SUGGAR.RECURSION}_${performance
|
177
|
-
.now()
|
178
|
-
.toString()
|
179
|
-
.replace('.', 7)}`
|
180
|
-
Drill.Variables.add(name)
|
181
|
-
Drill.Variables.add(newName)
|
182
|
-
Drill.Helpers.add('__tco')
|
183
|
-
const functionArgs = Arguments.at(-1).slice(1)
|
184
|
-
const body = functionArgs.pop()
|
185
|
-
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
186
|
-
deepRename(n, `()=>${newName}`, body)
|
187
|
-
const evaluatedBody = compile(body, FunctionDrill)
|
188
|
-
const vars = FunctionDrill.Variables.size
|
189
|
-
? `var ${[...FunctionDrill.Variables].join(',')};`
|
190
|
-
: ''
|
191
|
-
return `(${name}=(__tco(${newName}=(${parseArgs(
|
192
|
-
functionArgs,
|
193
|
-
Drill
|
194
|
-
)})=>{${vars}return ${evaluatedBody
|
195
|
-
.toString()
|
196
|
-
.trim()}}, ${newName})));`
|
197
|
-
} else if (prefix === SUGGAR.CACHE) {
|
198
|
-
// memoization here
|
199
|
-
const name = lispToJavaScriptVariableName(n)
|
200
|
-
const newName = name.substring(SUGGAR.CACHE.length + 1)
|
201
|
-
Drill.Variables.add(name)
|
202
|
-
const functionArgs = Arguments.at(-1).slice(1)
|
203
|
-
const body = functionArgs.pop()
|
204
|
-
deepRename(n, newName, body)
|
205
|
-
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
206
|
-
const evaluatedBody = compile(body, FunctionDrill)
|
207
|
-
const vars = FunctionDrill.Variables.size
|
208
|
-
? `var ${[...FunctionDrill.Variables].join(',')};`
|
209
|
-
: ''
|
210
|
-
return `(${name}=function(){var __${newName}_map = new Map();var ${newName}=(function(${parseArgs(
|
211
|
-
functionArgs,
|
212
|
-
Drill
|
213
|
-
)}){${vars};var __key=[...arguments].join(',');if(__${newName}_map.has(__key)){return __${newName}_map.get(__key)}else{var __res = ${evaluatedBody
|
214
|
-
.toString()
|
215
|
-
.trim()};__${newName}_map.set(__key, __res);return __res}});return ${newName}(...arguments)});`
|
216
|
-
} else {
|
217
|
-
const name = lispToJavaScriptVariableName(n)
|
218
|
-
Drill.Variables.add(name)
|
219
|
-
return `${name}=${compile(Arguments[1], Drill)};`
|
220
|
-
}
|
221
|
-
}
|
222
|
-
case KEYWORDS.IS_ATOM:
|
223
|
-
Drill.Helpers.add('atom_predicate')
|
224
|
-
return `atom_predicate(${compile(Arguments[0], Drill)});`
|
225
|
-
case KEYWORDS.IS_LAMBDA:
|
226
|
-
Drill.Helpers.add('lambda_predicate')
|
227
|
-
return `lambda_predicate(${compile(Arguments[0], Drill)});`
|
228
|
-
case KEYWORDS.ARRAY_TYPE:
|
229
|
-
return `[${parseArgs(Arguments, Drill)}];`
|
230
|
-
case KEYWORDS.ARRAY_LENGTH:
|
231
|
-
Drill.Helpers.add('length')
|
232
|
-
return `length(${compile(Arguments[0], Drill)})`
|
233
|
-
case KEYWORDS.GET_ARRAY:
|
234
|
-
Drill.Helpers.add('get')
|
235
|
-
return `get(${compile(Arguments[0], Drill)}, ${compile(
|
236
|
-
Arguments[1],
|
237
|
-
Drill
|
238
|
-
)});`
|
239
|
-
case KEYWORDS.ANONYMOUS_FUNCTION: {
|
240
|
-
const functionArgs = Arguments
|
241
|
-
const body = Arguments.pop()
|
242
|
-
const InnerDrills = { Variables: new Set(), Helpers: Drill.Helpers }
|
243
|
-
const evaluatedBody = compile(body, InnerDrills)
|
244
|
-
const vars = InnerDrills.Variables.size
|
245
|
-
? `var ${[...InnerDrills.Variables].join(',')};`
|
246
|
-
: ''
|
247
|
-
const args = parseArgs(
|
248
|
-
functionArgs.map((node, index) =>
|
249
|
-
node[VALUE] === PLACEHOLDER
|
250
|
-
? leaf(node[TYPE], `_${index}`)
|
251
|
-
: leaf(node[TYPE], node[VALUE])
|
252
|
-
),
|
253
|
-
InnerDrills
|
254
|
-
)
|
255
|
-
// const $ = [${args}];
|
256
|
-
return `((${args})=>{${vars}return ${evaluatedBody
|
257
|
-
.toString()
|
258
|
-
.trim()}});`
|
259
|
-
}
|
260
|
-
case KEYWORDS.AND:
|
261
|
-
return `((${parseArgs(Arguments, Drill, '&&')}) ? 1 : 0);`
|
262
|
-
case KEYWORDS.OR:
|
263
|
-
return `((${parseArgs(Arguments, Drill, '||')}) ? 1 : 0);`
|
264
|
-
case KEYWORDS.EQUAL:
|
265
|
-
return `+(${parseArgs(Arguments, Drill, '===')});`
|
266
|
-
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
267
|
-
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
268
|
-
case KEYWORDS.GREATHER_THAN:
|
269
|
-
case KEYWORDS.LESS_THAN:
|
270
|
-
return `+(${parseArgs(Arguments, Drill, token)});`
|
271
|
-
case KEYWORDS.SUBTRACTION:
|
272
|
-
return Arguments.length === 1
|
273
|
-
? `(-${compile(Arguments[0], Drill)});`
|
274
|
-
: `(${parse(Arguments, Drill)
|
275
|
-
// Add space so it doesn't consider it 2--1 but 2- -1
|
276
|
-
.map((x) => (typeof x === 'number' && x < 0 ? ` ${x}` : x))
|
277
|
-
.join(token)});`
|
278
|
-
case KEYWORDS.MULTIPLICATION:
|
279
|
-
return `(${parseArgs(Arguments, Drill, token)});`
|
280
|
-
case KEYWORDS.DIVISION:
|
281
|
-
return Arguments.length === 1
|
282
|
-
? `(1/${compile(Arguments[0], Drill)});`
|
283
|
-
: `(${parseArgs(Arguments, Drill, token)});`
|
284
|
-
case KEYWORDS.ADDITION:
|
285
|
-
return `(${parseArgs(Arguments, Drill, token)});`
|
286
|
-
case KEYWORDS.BITWISE_AND:
|
287
|
-
case KEYWORDS.BITWISE_OR:
|
288
|
-
case KEYWORDS.BITWISE_XOR:
|
289
|
-
case KEYWORDS.BITWISE_LEFT_SHIFT:
|
290
|
-
case KEYWORDS.BITWISE_RIGHT_SHIFT:
|
291
|
-
case KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT:
|
292
|
-
return `(${parseArgs(Arguments, Drill, token)});`
|
293
|
-
case KEYWORDS.REMAINDER_OF_DIVISION:
|
294
|
-
return `(${compile(Arguments[0], Drill)}%${compile(
|
295
|
-
Arguments[1],
|
296
|
-
Drill
|
297
|
-
)});`
|
298
|
-
case KEYWORDS.BIT_TYPE:
|
299
|
-
return `(${compile(Arguments[0], Drill)}>>>0).toString(2)`
|
300
|
-
case KEYWORDS.BITWISE_NOT:
|
301
|
-
return `~(${compile(Arguments[0], Drill)})`
|
302
|
-
case KEYWORDS.NOT:
|
303
|
-
return `(+!${compile(Arguments[0], Drill)})`
|
304
|
-
case KEYWORDS.IF: {
|
305
|
-
return `(${compile(Arguments[0], Drill)}?${compile(
|
306
|
-
Arguments[1],
|
307
|
-
Drill
|
308
|
-
)}:${Arguments.length === 3 ? compile(Arguments[2], Drill) : 0});`
|
309
|
-
}
|
310
|
-
case KEYWORDS.THROW: {
|
311
|
-
Drill.Helpers.add('__error')
|
312
|
-
return `__error(${compile(Arguments[0], Drill)})`
|
313
|
-
}
|
314
|
-
default: {
|
315
|
-
const camelCased = lispToJavaScriptVariableName(token)
|
316
|
-
if (camelCased in Helpers) Drill.Helpers.add(camelCased)
|
317
|
-
return `${camelCased}(${parseArgs(Arguments, Drill)});`
|
318
|
-
}
|
319
|
-
}
|
320
|
-
} else if (first[TYPE] === ATOM) return first[VALUE]
|
321
|
-
else if (first[TYPE] === WORD) {
|
322
|
-
const camelCased = lispToJavaScriptVariableName(token)
|
323
|
-
if (camelCased in Helpers) Drill.Helpers.add(camelCased)
|
324
|
-
return camelCased
|
325
|
-
}
|
326
|
-
}
|
327
|
-
const HelpersEntries = new Map(Object.entries(Helpers))
|
328
|
-
export const comp = (ast) => {
|
329
|
-
const Drill = { Variables: new Set(), Helpers: new Set() }
|
330
|
-
const raw = AST.parse(AST.stringify(ast)) // cloning for fn renames mutations
|
331
|
-
.map((tree) => compile(tree, Drill))
|
332
|
-
.filter((x) => x !== undefined)
|
333
|
-
.join('\n')
|
334
|
-
let program = ''
|
335
|
-
for (let i = 0; i < raw.length; ++i) {
|
336
|
-
const current = raw[i]
|
337
|
-
const next = raw[i + 1]
|
338
|
-
if (!semiColumnEdgeCases.has(current + next)) program += current
|
339
|
-
}
|
340
|
-
const help = Drill.Helpers.size
|
341
|
-
? `var ${[...Drill.Helpers.keys()]
|
342
|
-
.map((x) => HelpersEntries.get(x))
|
343
|
-
.join(',')};\n`
|
344
|
-
: ''
|
345
|
-
const vars = Drill.Variables.size
|
346
|
-
? `var ${[...Drill.Variables].join(',')};`
|
347
|
-
: ''
|
348
|
-
const top = `${help}${vars}`
|
349
|
-
return `${top}${program}`
|
350
|
-
}
|
1
|
+
import {
|
2
|
+
APPLY,
|
3
|
+
ATOM,
|
4
|
+
PLACEHOLDER,
|
5
|
+
KEYWORDS,
|
6
|
+
TYPE,
|
7
|
+
VALUE,
|
8
|
+
WORD,
|
9
|
+
SUGGAR
|
10
|
+
} from './keywords.js'
|
11
|
+
import { leaf, isLeaf, AST } from './parser.js'
|
12
|
+
const deepRename = (name, newName, tree) => {
|
13
|
+
if (!isLeaf(tree))
|
14
|
+
for (const leaf of tree) {
|
15
|
+
// Figure out a non mutable solution so
|
16
|
+
// I can get rid of deep clone AST.parse(AST.stringify(ast))
|
17
|
+
if (leaf[VALUE] === name) leaf[VALUE] = newName
|
18
|
+
deepRename(name, newName, leaf)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
|
22
|
+
const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
|
23
|
+
const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
|
24
|
+
const moduleNameToLodashes = (name) => name.replace(new RegExp(/:/g), '_')
|
25
|
+
const questionMarkToPredicate = (name) =>
|
26
|
+
name.replace(new RegExp(/\?/g), '_predicate')
|
27
|
+
const exclamationMarkMarkToEffect = (name) =>
|
28
|
+
name.replace(new RegExp(/\!/g), '_effect')
|
29
|
+
const toCamelCase = (name) => {
|
30
|
+
let out = name[0]
|
31
|
+
for (let i = 1; i < name.length; ++i) {
|
32
|
+
const current = name[i],
|
33
|
+
prev = name[i - 1]
|
34
|
+
if (current === '-') continue
|
35
|
+
else if (prev === '-') out += current.toUpperCase()
|
36
|
+
else out += current
|
37
|
+
}
|
38
|
+
return out
|
39
|
+
}
|
40
|
+
const dashToLodashes = (name) => name.replace(new RegExp(/-/g), '_')
|
41
|
+
const keywordToHelper = (name) => {
|
42
|
+
switch (name) {
|
43
|
+
case KEYWORDS.ADDITION:
|
44
|
+
return '__add'
|
45
|
+
case KEYWORDS.MULTIPLICATION:
|
46
|
+
return '__mult'
|
47
|
+
case KEYWORDS.DIVISION:
|
48
|
+
return '__div'
|
49
|
+
case KEYWORDS.SUBTRACTION:
|
50
|
+
return '__sub'
|
51
|
+
case KEYWORDS.GREATHER_THAN:
|
52
|
+
return '__gt'
|
53
|
+
case KEYWORDS.EQUAL:
|
54
|
+
return '__eq'
|
55
|
+
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
56
|
+
return '__gteq'
|
57
|
+
case KEYWORDS.LESS_THAN:
|
58
|
+
return '__lt'
|
59
|
+
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
60
|
+
return '__lteq'
|
61
|
+
default:
|
62
|
+
return name
|
63
|
+
}
|
64
|
+
}
|
65
|
+
const lispToJavaScriptVariableName = (name) =>
|
66
|
+
dashToLodashes(
|
67
|
+
arrowFromTo(
|
68
|
+
dotNamesToEmpty(
|
69
|
+
exclamationMarkMarkToEffect(
|
70
|
+
questionMarkToPredicate(
|
71
|
+
moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
|
72
|
+
)
|
73
|
+
)
|
74
|
+
)
|
75
|
+
)
|
76
|
+
)
|
77
|
+
const Helpers = {
|
78
|
+
__add: `__add=(a,b)=>{return a+b}`,
|
79
|
+
__sub: `__sub=function(a,b){return arguments.length===1?-a:a-b}`,
|
80
|
+
__mult: `__mult=(a,b)=>{return a*b}`,
|
81
|
+
__div: `__div=function(a,b){return arguments.length===1?1/a:a/b}`,
|
82
|
+
__gteq: '__gteq=(a,b)=>+(a>=b)',
|
83
|
+
__gt: '__gt=(a,b)=>+(a>b)',
|
84
|
+
__eq: '__eq=(a,b)=>+(a===b)',
|
85
|
+
__lteq: '__lteq=(a,b)=>+(a<=b)',
|
86
|
+
__lt: '__lt=(a,b)=>+(a<b)',
|
87
|
+
array: 'array=(...args)=>args',
|
88
|
+
not: 'not=(a)=>+!a',
|
89
|
+
and: `and=(...args)=>{
|
90
|
+
let circuit;
|
91
|
+
for(let i=0; i<args.length-1;++i){
|
92
|
+
circuit=args[i]
|
93
|
+
if(circuit) continue
|
94
|
+
else return 0
|
95
|
+
}
|
96
|
+
return args.at(-1) ? 1 : 0
|
97
|
+
}`,
|
98
|
+
or: `or=(...args)=>{
|
99
|
+
let circuit;
|
100
|
+
for(let i=0;i<args.length-1;++i) {
|
101
|
+
circuit = args[i]
|
102
|
+
if(circuit) return 1
|
103
|
+
else continue
|
104
|
+
}
|
105
|
+
return args.at(-1) ? 1 : 0
|
106
|
+
}`,
|
107
|
+
log_effect: `log_effect=(msg)=>{console.log(msg);return msg}`,
|
108
|
+
log_char_effect: `log_char_effect=(msg)=>{console.log(String.fromCharCode(msg));return msg}`,
|
109
|
+
log_string_effect: `log_string_effect=(msg)=>{console.log(msg.map(x=>String.fromCharCode(x)).join(''));return msg}`,
|
110
|
+
clear_effect: `clear_effect=()=>{console.clear();return 0}`,
|
111
|
+
get: 'get=(arr,i)=>arr.at(i)',
|
112
|
+
length: 'length=(arr)=>arr.length',
|
113
|
+
__tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
|
114
|
+
atom_predicate: `atom_predicate=(number)=>+(typeof number==='number')`,
|
115
|
+
lambda_predicate: `lambda_predicate=(fm)=>+(typeof fn==='function')`,
|
116
|
+
set_effect: `set_effect=function(array,index,value){if(arguments.length===1){array.pop()}else{array[index] = value};return array}`,
|
117
|
+
__error: `__error=(error)=>{throw new Error(error.map((x)=>String.fromCharCode(x)).join(''))}`
|
118
|
+
}
|
119
|
+
const semiColumnEdgeCases = new Set([
|
120
|
+
';)',
|
121
|
+
';-',
|
122
|
+
';+',
|
123
|
+
';*',
|
124
|
+
';%',
|
125
|
+
';&',
|
126
|
+
';/',
|
127
|
+
';:',
|
128
|
+
';.',
|
129
|
+
';=',
|
130
|
+
';<',
|
131
|
+
';>',
|
132
|
+
';|',
|
133
|
+
';,',
|
134
|
+
';?',
|
135
|
+
',,',
|
136
|
+
';;',
|
137
|
+
';]',
|
138
|
+
';^'
|
139
|
+
])
|
140
|
+
const parse = (Arguments, Drill) => Arguments.map((x) => compile(x, Drill))
|
141
|
+
const parseArgs = (Arguments, Drill, separator = ',') =>
|
142
|
+
parse(Arguments, Drill).join(separator)
|
143
|
+
const compile = (tree, Drill) => {
|
144
|
+
if (!tree) return ''
|
145
|
+
const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
|
146
|
+
if (first == undefined) return '[];'
|
147
|
+
const token = first[VALUE]
|
148
|
+
if (first[TYPE] === APPLY) {
|
149
|
+
switch (token) {
|
150
|
+
case KEYWORDS.BLOCK: {
|
151
|
+
if (Arguments.length > 1) {
|
152
|
+
return `(${Arguments.map((x) =>
|
153
|
+
(compile(x, Drill) ?? '').toString().trim()
|
154
|
+
)
|
155
|
+
.filter((x) => x !== undefined)
|
156
|
+
.join(',')});`
|
157
|
+
} else {
|
158
|
+
const res = compile(Arguments[0], Drill)
|
159
|
+
return res !== undefined ? res.toString().trim() : ''
|
160
|
+
}
|
161
|
+
}
|
162
|
+
case KEYWORDS.CALL_FUNCTION: {
|
163
|
+
const [first, ...rest] = Arguments
|
164
|
+
const apply = compile(first, Drill)
|
165
|
+
return `${
|
166
|
+
apply[apply.length - 1] === ';'
|
167
|
+
? apply.substring(0, apply.length - 1)
|
168
|
+
: apply
|
169
|
+
}(${parseArgs(rest, Drill)})`
|
170
|
+
}
|
171
|
+
case KEYWORDS.DEFINE_VARIABLE: {
|
172
|
+
const n = Arguments[0][VALUE]
|
173
|
+
const prefix = n.split(':')[0]
|
174
|
+
if (prefix === SUGGAR.RECURSION) {
|
175
|
+
const name = lispToJavaScriptVariableName(n)
|
176
|
+
const newName = `${SUGGAR.RECURSION}_${performance
|
177
|
+
.now()
|
178
|
+
.toString()
|
179
|
+
.replace('.', 7)}`
|
180
|
+
Drill.Variables.add(name)
|
181
|
+
Drill.Variables.add(newName)
|
182
|
+
Drill.Helpers.add('__tco')
|
183
|
+
const functionArgs = Arguments.at(-1).slice(1)
|
184
|
+
const body = functionArgs.pop()
|
185
|
+
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
186
|
+
deepRename(n, `()=>${newName}`, body)
|
187
|
+
const evaluatedBody = compile(body, FunctionDrill)
|
188
|
+
const vars = FunctionDrill.Variables.size
|
189
|
+
? `var ${[...FunctionDrill.Variables].join(',')};`
|
190
|
+
: ''
|
191
|
+
return `(${name}=(__tco(${newName}=(${parseArgs(
|
192
|
+
functionArgs,
|
193
|
+
Drill
|
194
|
+
)})=>{${vars}return ${evaluatedBody
|
195
|
+
.toString()
|
196
|
+
.trim()}}, ${newName})));`
|
197
|
+
} else if (prefix === SUGGAR.CACHE) {
|
198
|
+
// memoization here
|
199
|
+
const name = lispToJavaScriptVariableName(n)
|
200
|
+
const newName = name.substring(SUGGAR.CACHE.length + 1)
|
201
|
+
Drill.Variables.add(name)
|
202
|
+
const functionArgs = Arguments.at(-1).slice(1)
|
203
|
+
const body = functionArgs.pop()
|
204
|
+
deepRename(n, newName, body)
|
205
|
+
const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
|
206
|
+
const evaluatedBody = compile(body, FunctionDrill)
|
207
|
+
const vars = FunctionDrill.Variables.size
|
208
|
+
? `var ${[...FunctionDrill.Variables].join(',')};`
|
209
|
+
: ''
|
210
|
+
return `(${name}=function(){var __${newName}_map = new Map();var ${newName}=(function(${parseArgs(
|
211
|
+
functionArgs,
|
212
|
+
Drill
|
213
|
+
)}){${vars};var __key=[...arguments].join(',');if(__${newName}_map.has(__key)){return __${newName}_map.get(__key)}else{var __res = ${evaluatedBody
|
214
|
+
.toString()
|
215
|
+
.trim()};__${newName}_map.set(__key, __res);return __res}});return ${newName}(...arguments)});`
|
216
|
+
} else {
|
217
|
+
const name = lispToJavaScriptVariableName(n)
|
218
|
+
Drill.Variables.add(name)
|
219
|
+
return `${name}=${compile(Arguments[1], Drill)};`
|
220
|
+
}
|
221
|
+
}
|
222
|
+
case KEYWORDS.IS_ATOM:
|
223
|
+
Drill.Helpers.add('atom_predicate')
|
224
|
+
return `atom_predicate(${compile(Arguments[0], Drill)});`
|
225
|
+
case KEYWORDS.IS_LAMBDA:
|
226
|
+
Drill.Helpers.add('lambda_predicate')
|
227
|
+
return `lambda_predicate(${compile(Arguments[0], Drill)});`
|
228
|
+
case KEYWORDS.ARRAY_TYPE:
|
229
|
+
return `[${parseArgs(Arguments, Drill)}];`
|
230
|
+
case KEYWORDS.ARRAY_LENGTH:
|
231
|
+
Drill.Helpers.add('length')
|
232
|
+
return `length(${compile(Arguments[0], Drill)})`
|
233
|
+
case KEYWORDS.GET_ARRAY:
|
234
|
+
Drill.Helpers.add('get')
|
235
|
+
return `get(${compile(Arguments[0], Drill)}, ${compile(
|
236
|
+
Arguments[1],
|
237
|
+
Drill
|
238
|
+
)});`
|
239
|
+
case KEYWORDS.ANONYMOUS_FUNCTION: {
|
240
|
+
const functionArgs = Arguments
|
241
|
+
const body = Arguments.pop()
|
242
|
+
const InnerDrills = { Variables: new Set(), Helpers: Drill.Helpers }
|
243
|
+
const evaluatedBody = compile(body, InnerDrills)
|
244
|
+
const vars = InnerDrills.Variables.size
|
245
|
+
? `var ${[...InnerDrills.Variables].join(',')};`
|
246
|
+
: ''
|
247
|
+
const args = parseArgs(
|
248
|
+
functionArgs.map((node, index) =>
|
249
|
+
node[VALUE] === PLACEHOLDER
|
250
|
+
? leaf(node[TYPE], `_${index}`)
|
251
|
+
: leaf(node[TYPE], node[VALUE])
|
252
|
+
),
|
253
|
+
InnerDrills
|
254
|
+
)
|
255
|
+
// const $ = [${args}];
|
256
|
+
return `((${args})=>{${vars}return ${evaluatedBody
|
257
|
+
.toString()
|
258
|
+
.trim()}});`
|
259
|
+
}
|
260
|
+
case KEYWORDS.AND:
|
261
|
+
return `((${parseArgs(Arguments, Drill, '&&')}) ? 1 : 0);`
|
262
|
+
case KEYWORDS.OR:
|
263
|
+
return `((${parseArgs(Arguments, Drill, '||')}) ? 1 : 0);`
|
264
|
+
case KEYWORDS.EQUAL:
|
265
|
+
return `+(${parseArgs(Arguments, Drill, '===')});`
|
266
|
+
case KEYWORDS.GREATHER_THAN_OR_EQUAL:
|
267
|
+
case KEYWORDS.LESS_THAN_OR_EQUAL:
|
268
|
+
case KEYWORDS.GREATHER_THAN:
|
269
|
+
case KEYWORDS.LESS_THAN:
|
270
|
+
return `+(${parseArgs(Arguments, Drill, token)});`
|
271
|
+
case KEYWORDS.SUBTRACTION:
|
272
|
+
return Arguments.length === 1
|
273
|
+
? `(-${compile(Arguments[0], Drill)});`
|
274
|
+
: `(${parse(Arguments, Drill)
|
275
|
+
// Add space so it doesn't consider it 2--1 but 2- -1
|
276
|
+
.map((x) => (typeof x === 'number' && x < 0 ? ` ${x}` : x))
|
277
|
+
.join(token)});`
|
278
|
+
case KEYWORDS.MULTIPLICATION:
|
279
|
+
return `(${parseArgs(Arguments, Drill, token)});`
|
280
|
+
case KEYWORDS.DIVISION:
|
281
|
+
return Arguments.length === 1
|
282
|
+
? `(1/${compile(Arguments[0], Drill)});`
|
283
|
+
: `(${parseArgs(Arguments, Drill, token)});`
|
284
|
+
case KEYWORDS.ADDITION:
|
285
|
+
return `(${parseArgs(Arguments, Drill, token)});`
|
286
|
+
case KEYWORDS.BITWISE_AND:
|
287
|
+
case KEYWORDS.BITWISE_OR:
|
288
|
+
case KEYWORDS.BITWISE_XOR:
|
289
|
+
case KEYWORDS.BITWISE_LEFT_SHIFT:
|
290
|
+
case KEYWORDS.BITWISE_RIGHT_SHIFT:
|
291
|
+
case KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT:
|
292
|
+
return `(${parseArgs(Arguments, Drill, token)});`
|
293
|
+
case KEYWORDS.REMAINDER_OF_DIVISION:
|
294
|
+
return `(${compile(Arguments[0], Drill)}%${compile(
|
295
|
+
Arguments[1],
|
296
|
+
Drill
|
297
|
+
)});`
|
298
|
+
case KEYWORDS.BIT_TYPE:
|
299
|
+
return `(${compile(Arguments[0], Drill)}>>>0).toString(2)`
|
300
|
+
case KEYWORDS.BITWISE_NOT:
|
301
|
+
return `~(${compile(Arguments[0], Drill)})`
|
302
|
+
case KEYWORDS.NOT:
|
303
|
+
return `(+!${compile(Arguments[0], Drill)})`
|
304
|
+
case KEYWORDS.IF: {
|
305
|
+
return `(${compile(Arguments[0], Drill)}?${compile(
|
306
|
+
Arguments[1],
|
307
|
+
Drill
|
308
|
+
)}:${Arguments.length === 3 ? compile(Arguments[2], Drill) : 0});`
|
309
|
+
}
|
310
|
+
case KEYWORDS.THROW: {
|
311
|
+
Drill.Helpers.add('__error')
|
312
|
+
return `__error(${compile(Arguments[0], Drill)})`
|
313
|
+
}
|
314
|
+
default: {
|
315
|
+
const camelCased = lispToJavaScriptVariableName(token)
|
316
|
+
if (camelCased in Helpers) Drill.Helpers.add(camelCased)
|
317
|
+
return `${camelCased}(${parseArgs(Arguments, Drill)});`
|
318
|
+
}
|
319
|
+
}
|
320
|
+
} else if (first[TYPE] === ATOM) return first[VALUE]
|
321
|
+
else if (first[TYPE] === WORD) {
|
322
|
+
const camelCased = lispToJavaScriptVariableName(token)
|
323
|
+
if (camelCased in Helpers) Drill.Helpers.add(camelCased)
|
324
|
+
return camelCased
|
325
|
+
}
|
326
|
+
}
|
327
|
+
const HelpersEntries = new Map(Object.entries(Helpers))
|
328
|
+
export const comp = (ast) => {
|
329
|
+
const Drill = { Variables: new Set(), Helpers: new Set() }
|
330
|
+
const raw = AST.parse(AST.stringify(ast)) // cloning for fn renames mutations
|
331
|
+
.map((tree) => compile(tree, Drill))
|
332
|
+
.filter((x) => x !== undefined)
|
333
|
+
.join('\n')
|
334
|
+
let program = ''
|
335
|
+
for (let i = 0; i < raw.length; ++i) {
|
336
|
+
const current = raw[i]
|
337
|
+
const next = raw[i + 1]
|
338
|
+
if (!semiColumnEdgeCases.has(current + next)) program += current
|
339
|
+
}
|
340
|
+
const help = Drill.Helpers.size
|
341
|
+
? `var ${[...Drill.Helpers.keys()]
|
342
|
+
.map((x) => HelpersEntries.get(x))
|
343
|
+
.join(',')};\n`
|
344
|
+
: ''
|
345
|
+
const vars = Drill.Variables.size
|
346
|
+
? `var ${[...Drill.Variables].join(',')};`
|
347
|
+
: ''
|
348
|
+
const top = `${help}${vars}`
|
349
|
+
return `${top}${program}`
|
350
|
+
}
|