fez-lisp 1.1.9 → 1.1.10

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "fez-lisp",
3
3
  "description": "Lisp interpreted & compiled to JavaScript",
4
4
  "author": "AT290690",
5
- "version": "1.1.9",
5
+ "version": "1.1.10",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "keywords": [
package/src/compiler.js CHANGED
@@ -239,7 +239,7 @@ const compile = (tree, Drill) => {
239
239
  Drill.Variables.add(name)
240
240
  Drill.Variables.add(newName)
241
241
  Drill.Helpers.add('__tco')
242
- const functionArgs = Arguments[1].slice(1)
242
+ const functionArgs = val.slice(1)
243
243
  const body = functionArgs.pop()
244
244
  const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
245
245
  deepRename(arg[VALUE], newName, body)
@@ -263,8 +263,6 @@ const compile = (tree, Drill) => {
263
263
  return `(${parseArgs(Arguments, Drill, '&&')});`
264
264
  case KEYWORDS.OR:
265
265
  return `((${parseArgs(Arguments, Drill, '||')}) || 0);`
266
- case KEYWORDS.CONCATENATION:
267
- return '(' + parseArgs(Arguments, Drill, '+') + ');'
268
266
  case KEYWORDS.EQUAL:
269
267
  return `+(${parseArgs(Arguments, Drill, '===')});`
270
268
  case KEYWORDS.GREATHER_THAN_OR_EQUAL:
package/src/ocaml.js ADDED
@@ -0,0 +1,373 @@
1
+ import {
2
+ APPLY,
3
+ ATOM,
4
+ PLACEHOLDER,
5
+ KEYWORDS,
6
+ TYPE,
7
+ VALUE,
8
+ WORD
9
+ } from './keywords.js'
10
+ import { leaf, isLeaf } from './parser.js'
11
+ import { deepRename } from './utils.js'
12
+ const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
13
+ const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
14
+ const commaToLodash = (name) => name.replace(new RegExp(/\,/g), '_')
15
+ const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
16
+ const moduleNameToLodashes = (name) => name.replace(new RegExp(/:/g), '_')
17
+ const questionMarkToPredicate = (name) =>
18
+ name.replace(new RegExp(/\?/g), 'Predicate')
19
+ const exclamationMarkMarkToEffect = (name) =>
20
+ name.replace(new RegExp(/\!/g), 'Effect')
21
+ const toCamelCase = (name) => {
22
+ let out = name[0]
23
+ for (let i = 1; i < name.length; ++i) {
24
+ const current = name[i],
25
+ prev = name[i - 1]
26
+ if (current === '-') continue
27
+ else if (prev === '-') out += current.toUpperCase()
28
+ else out += current
29
+ }
30
+ return out
31
+ }
32
+ const keywordToHelper = (name) => {
33
+ switch (name) {
34
+ case KEYWORDS.ADDITION:
35
+ return '__add'
36
+ case KEYWORDS.MULTIPLICATION:
37
+ return '__mult'
38
+ case KEYWORDS.SUBTRACTION:
39
+ return '__sub'
40
+ case KEYWORDS.GREATHER_THAN:
41
+ return '__gt'
42
+ case KEYWORDS.EQUAL:
43
+ return '__eq'
44
+ case KEYWORDS.GREATHER_THAN_OR_EQUAL:
45
+ return '__gteq'
46
+ case KEYWORDS.LESS_THAN:
47
+ return '__lt'
48
+ case KEYWORDS.LESS_THAN_OR_EQUAL:
49
+ return '__lteq'
50
+ default:
51
+ return name
52
+ }
53
+ }
54
+ const lispToJavaScriptVariableName = (name) =>
55
+ toCamelCase(
56
+ arrowFromTo(
57
+ dotNamesToEmpty(
58
+ exclamationMarkMarkToEffect(
59
+ questionMarkToPredicate(
60
+ commaToLodash(
61
+ moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
62
+ )
63
+ )
64
+ )
65
+ )
66
+ )
67
+ )
68
+ const Helpers = {
69
+ // __string: `__string=(...args)=>{const str=args.flat();str.isString=true;return str}`,
70
+ // __add: `__add=(...numbers)=>{return numbers.reduce((a,b)=>a+b,0)}`,
71
+ // __sub: `__sub=(...numbers)=>{return numbers.reduce((a,b)=>a-b,0)}`,
72
+ // __mult: `__mult=(...numbers)=>{return numbers.reduce((a,b)=>a*b,1)}`,
73
+ // __gteq: '__gteq=(a,b)=>+(a>=b)',
74
+ // __gt: '__gt=(a,b)=>+(a>b)',
75
+ // __eq: '__eq=(a,b)=>+(a===b)',
76
+ // __lteq: '__lteq=(a,b)=>+(a<=b)',
77
+ // __lt: '__lt=(a,b)=>+(a<b)',
78
+ // not: 'not=(a)=>+!a',
79
+ // and: `and=(...args)=>{
80
+ // let circuit;
81
+ // for(let i=0; i<args.length-1;++i){
82
+ // circuit=args[i]
83
+ // if(circuit) continue
84
+ // else return circuit
85
+ // }
86
+ // return args.at(-1)
87
+ // }`,
88
+ // or: `or=(...args)=>{
89
+ // let circuit;
90
+ // for(let i=0;i<args.length-1;++i) {
91
+ // circuit = args[i]
92
+ // if(circuit)return circuit
93
+ // else continue
94
+ // }
95
+ // return args.at(-1)
96
+ // }`,
97
+ // logEffect: `logEffect=(msg)=>{console.log(msg);return msg}`,
98
+ // clearEffect: `clearEffect=()=>{console.clear();return 0}`,
99
+ array_cons: `rec array_cons lists =
100
+ match lists with
101
+ | [] -> []
102
+ | hd::tl -> hd @ array_cons tl`,
103
+ car: 'car = fun (arr::_) -> arr',
104
+ cdr: 'cdr = fun (_::arr) -> arr',
105
+ array_get: `rec array_get = function
106
+ | [], _ -> raise (Failure "array_get")
107
+ | list, n when n < 0 -> array_get(list, List.length(list) - n)
108
+ | x::_, 0 -> x
109
+ | x::xs, n -> array_get(xs, n - 1)`,
110
+ length: 'length(arr)=float_of_int(List.length(arr))'
111
+ // numberPredicate: `numberPredicate=(number)=>+(typeof number==='number')`,
112
+ // lambdaPredicate: `lambdaPredicate=(lambda)=>+(typeof lambda==='function')`,
113
+ // arrayPredicate: `arrayPredicate=(array)=>+Array.isArray(array)`,
114
+ // error: `error=(error)=>{throw new Error(error)}`,
115
+ // array_setEffect: `array_setEffect=(array,index,value)=>{if(index<0){const target=array.length+index;while(array.length!==target)array.pop()}else array[index] = value;return array}`
116
+ // array_setEffect: `array_setEffect=(array,index,value)=>{if(index<0){const target=array.length+index;while(array.length!==target)array.pop()}else array[index] = value;return array}`
117
+ }
118
+ const semiColumnEdgeCases = new Set([
119
+ ';)',
120
+ ';-',
121
+ ';+',
122
+ ';*',
123
+ ';%',
124
+ ';&',
125
+ ';/',
126
+ ';:',
127
+ ';.',
128
+ ';=',
129
+ ';<',
130
+ ';>',
131
+ ';|',
132
+ ';,',
133
+ ';?',
134
+ ',,',
135
+ // ';;',
136
+ ';]'
137
+ ])
138
+ const parse = (Arguments, Drill) => Arguments.map((x) => compile(x, Drill))
139
+ const parseArgs = (Arguments, Drill, separator = ',') =>
140
+ parse(Arguments, Drill).join(separator)
141
+ const compile = (tree, Drill) => {
142
+ if (!tree) return ''
143
+ const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
144
+ if (first == undefined) return '[];'
145
+ const token = first[VALUE]
146
+ if (first[TYPE] === APPLY) {
147
+ switch (token) {
148
+ case KEYWORDS.BLOCK: {
149
+ if (Arguments.length > 1) {
150
+ return `(${Arguments.map((x) =>
151
+ (compile(x, Drill) ?? '').toString().trimStart().replace(';;', '')
152
+ )
153
+ .filter(Boolean)
154
+ .join(' in\n')})`
155
+ } else {
156
+ const res = compile(Arguments[0], Drill)
157
+ return res !== undefined ? res.toString().trim() : ''
158
+ }
159
+ }
160
+ case KEYWORDS.CALL_FUNCTION: {
161
+ const [first, ...rest] = Arguments
162
+ const apply = compile(first, Drill)
163
+ return `${
164
+ apply[apply.length - 1] === ';'
165
+ ? apply.substring(0, apply.length - 1)
166
+ : apply
167
+ }(${parseArgs(rest, Drill)})`
168
+ }
169
+ case KEYWORDS.DEFINE_VARIABLE: {
170
+ let name,
171
+ out = 'let '
172
+ if (Arguments[0][TYPE] === WORD) {
173
+ name = lispToJavaScriptVariableName(Arguments[0][VALUE])
174
+ Drill.Variables.add(name)
175
+ }
176
+ out += `${name} = ${compile(Arguments[1], Drill)}`
177
+ out += `;;`
178
+ return out
179
+ }
180
+ case KEYWORDS.IS_NUMBER:
181
+ Drill.Helpers.add('numberPredicate')
182
+ return `numberPredicate(${compile(Arguments[0], Drill)});`
183
+ case KEYWORDS.IS_FUNCTION:
184
+ Drill.Helpers.add('lambdaPredicate')
185
+ return `lambdaPredicate(${compile(Arguments[0], Drill)});`
186
+ case KEYWORDS.IS_ARRAY:
187
+ Drill.Helpers.add('arrayPredicate')
188
+ return `arrayPredicate(${compile(Arguments[0], Drill)});`
189
+ case KEYWORDS.NUMBER_TYPE:
190
+ return '0'
191
+ case KEYWORDS.BOOLEAN_TYPE:
192
+ return '1'
193
+ case KEYWORDS.STRING_TYPE:
194
+ Drill.Helpers.add('__string')
195
+ return `__string(${parseArgs(Arguments, Drill)});`
196
+ case KEYWORDS.ARRAY_TYPE:
197
+ return Arguments.length === 2 &&
198
+ Arguments[1][TYPE] === WORD &&
199
+ Arguments[1][VALUE] === 'length'
200
+ ? `(List.make ${compile(Arguments[0], Drill)} 0)`
201
+ : `[${parseArgs(Arguments, Drill, ';')}]`
202
+ case KEYWORDS.ARRAY_LENGTH:
203
+ Drill.Helpers.add('length')
204
+ return `length(${compile(Arguments[0], Drill)})`
205
+ case KEYWORDS.FIRST_ARRAY:
206
+ Drill.Helpers.add('car')
207
+ return `car(${compile(Arguments[0], Drill)})`
208
+ case KEYWORDS.REST_ARRAY:
209
+ Drill.Helpers.add('cdr')
210
+ return `cdr(${compile(Arguments[0], Drill)})`
211
+ case KEYWORDS.GET_ARRAY:
212
+ Drill.Helpers.add('array_get')
213
+ return `array_get(${compile(
214
+ Arguments[0],
215
+ Drill
216
+ )}, int_of_float(${compile(Arguments[1], Drill)}))`
217
+ case KEYWORDS.CONS:
218
+ Drill.Helpers.add('array_cons')
219
+ return `array_cons[${parseArgs(Arguments, Drill, ';')}]`
220
+ case KEYWORDS.ANONYMOUS_FUNCTION: {
221
+ const functionArgs = Arguments
222
+ const body = Arguments.pop()
223
+ const InnerDrills = { Variables: new Set(), Helpers: Drill.Helpers }
224
+ const evaluatedBody = compile(body, InnerDrills)
225
+ // const vars = InnerDrills.Variables.size
226
+ // ? `let ${[...InnerDrills.Variables].join(',')};`
227
+ // : ''
228
+ return `(fun (${parseArgs(
229
+ functionArgs.map((node, index) =>
230
+ node[VALUE] === PLACEHOLDER
231
+ ? leaf(node[TYPE], `_${index}`)
232
+ : leaf(node[TYPE], node[VALUE])
233
+ ),
234
+ InnerDrills
235
+ )}) -> ${evaluatedBody.toString().trimStart()})`
236
+ }
237
+ case KEYWORDS.TAIL_CALLS_OPTIMISED_RECURSIVE_FUNCTION: {
238
+ const arg = Arguments[0]
239
+ const val = Arguments[1]
240
+ if (val[0][0] === APPLY && val[0][1] === KEYWORDS.ANONYMOUS_FUNCTION) {
241
+ const name = lispToJavaScriptVariableName(arg[VALUE])
242
+ const functionArgs = val.slice(1)
243
+ const body = functionArgs.pop()
244
+ const FunctionDrill = { Variables: new Set(), Helpers: Drill.Helpers }
245
+ const evaluatedBody = compile(body, FunctionDrill)
246
+ return `let rec ${name} (${parseArgs(
247
+ functionArgs.map((node, index) =>
248
+ node[VALUE] === PLACEHOLDER
249
+ ? leaf(node[TYPE], `_${index}`)
250
+ : leaf(node[TYPE], node[VALUE])
251
+ )
252
+ )}) = ${evaluatedBody.toString().trimStart()};;`
253
+ }
254
+ }
255
+ case KEYWORDS.AND:
256
+ return `(${parseArgs(Arguments, Drill, '&&')})`
257
+ case KEYWORDS.OR:
258
+ return `((${parseArgs(Arguments, Drill, '||')}) || 0.0)`
259
+ case KEYWORDS.EQUAL:
260
+ return `(if ${parseArgs(Arguments, Drill, '==')} then 1.0 else 0.0)`
261
+ case KEYWORDS.GREATHER_THAN_OR_EQUAL:
262
+ case KEYWORDS.LESS_THAN_OR_EQUAL:
263
+ case KEYWORDS.GREATHER_THAN:
264
+ case KEYWORDS.LESS_THAN:
265
+ return `(if ${parseArgs(Arguments, Drill, token)} then 1.0 else 0.0)`
266
+ case KEYWORDS.SUBTRACTION:
267
+ return Arguments.length === 1
268
+ ? `(-.${compile(Arguments[0], Drill)})`
269
+ : `(${parse(Arguments, Drill)
270
+ // Add space so it doesn't consider it 2--1 but 2- -1
271
+ .map((x) => (typeof x === 'number' && x < 0 ? ` ${x}` : x))
272
+ .join(`${token}.`)})`
273
+ case KEYWORDS.MULTIPLICATION:
274
+ return Arguments.length
275
+ ? `(${parseArgs(Arguments, Drill, `${token}.`)})`
276
+ : `(1.0)`
277
+ case KEYWORDS.DIVISION:
278
+ return Arguments.length
279
+ ? Arguments.length === 1
280
+ ? `(1.0/.${compile(Arguments[0], Drill)})`
281
+ : `(${parseArgs(Arguments, Drill, `${token}.`)})`
282
+ : `(0.0)`
283
+ case KEYWORDS.ADDITION:
284
+ return Arguments.length
285
+ ? `(${parseArgs(Arguments, Drill, `${token}.`)})`
286
+ : `(0.0)`
287
+ case KEYWORDS.BITWISE_AND:
288
+ case KEYWORDS.BITWISE_OR:
289
+ case KEYWORDS.BITWISE_XOR:
290
+ case KEYWORDS.BITWISE_LEFT_SHIFT:
291
+ case KEYWORDS.BITWISE_RIGHT_SHIFT:
292
+ case KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT:
293
+ return `(${parseArgs(Arguments, Drill, token)})`
294
+ case KEYWORDS.REMAINDER_OF_DIVISION:
295
+ return `(mod_float ${compile(Arguments[0], Drill)} ${compile(
296
+ Arguments[1],
297
+ Drill
298
+ )})`
299
+ case KEYWORDS.BIT_TYPE:
300
+ return `(${compile(Arguments[0], Drill)}>>>0).toString(2)`
301
+ case KEYWORDS.BITWISE_NOT:
302
+ return `~(${compile(Arguments[0], Drill)})`
303
+ case KEYWORDS.NOT:
304
+ return `(if ${compile(Arguments[0], Drill)} == 0.0 then 1.0 else 0.0)`
305
+ case KEYWORDS.IF: {
306
+ return `(if ${compile(Arguments[0], Drill)} != 0.0 then ${compile(
307
+ Arguments[1],
308
+ Drill
309
+ )} else ${Arguments.length === 3 ? compile(Arguments[2], Drill) : 0})`
310
+ }
311
+ case KEYWORDS.CONDITION: {
312
+ let out = ''
313
+ for (let i = 0; i < Arguments.length; i += 2)
314
+ out += `(if ${compile(Arguments[i], Drill)} != 0.0 then ${compile(
315
+ Arguments[i + 1],
316
+ Drill
317
+ )} else `
318
+ out += '0.0)'
319
+ return out
320
+ }
321
+ // case KEYWORDS.PIPE: {
322
+ // let inp = Arguments[0]
323
+ // for (let i = 1; i < Arguments.length; ++i)
324
+ // inp = [Arguments[i].shift(), inp, ...Arguments[i]]
325
+ // return compile(inp, Drill)
326
+ // }
327
+ case KEYWORDS.IMMUTABLE_FUNCTION: {
328
+ const [first, ...rest] = Arguments
329
+ return compile([leaf(APPLY, first[VALUE]), ...rest], Drill)
330
+ }
331
+ case KEYWORDS.NOT_COMPILED_BLOCK:
332
+ case KEYWORDS.TEST_CASE:
333
+ case KEYWORDS.TEST_BED:
334
+ case KEYWORDS.DOC:
335
+ return ''
336
+ default: {
337
+ const camelCased = lispToJavaScriptVariableName(token)
338
+ if (camelCased in Helpers) Drill.Helpers.add(camelCased)
339
+ return `${camelCased}(${parseArgs(Arguments, Drill)})`
340
+ }
341
+ }
342
+ } else if (first[TYPE] === ATOM)
343
+ return Number.isInteger(first[TYPE])
344
+ ? `${first[VALUE]}.0`
345
+ : first[VALUE].toString()
346
+ else if (first[TYPE] === WORD) {
347
+ const camelCased = lispToJavaScriptVariableName(token)
348
+ if (camelCased in Helpers) Drill.Helpers.add(camelCased)
349
+ return camelCased
350
+ }
351
+ }
352
+ const HelpersEntries = new Map(Object.entries(Helpers))
353
+ export const comp = (ast) => {
354
+ const Drill = { Variables: new Set(), Helpers: new Set() }
355
+ const raw = ast
356
+ .map((tree) => compile(tree, Drill))
357
+ .filter(Boolean)
358
+ .join('\n')
359
+ let program = ''
360
+ for (let i = 0; i < raw.length; ++i) {
361
+ const current = raw[i]
362
+ const next = raw[i + 1]
363
+ if (!semiColumnEdgeCases.has(current + next)) program += current
364
+ }
365
+ const help = Drill.Helpers.size
366
+ ? `${[...Drill.Helpers.keys()]
367
+ .map((x) => `let ${HelpersEntries.get(x)};;`)
368
+ .join('\n')}\n`
369
+ : ''
370
+
371
+ const top = `${help}`
372
+ return { top, program }
373
+ }
package/src/utils.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import std from '../lib/baked/std.js'
2
- import { comp } from './compiler.js'
2
+ import { comp as JavaScript } from './compiler.js'
3
+ import { comp as OCaml } from './ocaml.js'
4
+
3
5
  import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './keywords.js'
4
6
  import { run } from './evaluator.js'
5
7
  import { AST, isLeaf, LISP } from './parser.js'
@@ -178,8 +180,16 @@ export const fez = (source, options = {}) => {
178
180
  )
179
181
  const ast = [...treeShake(parsed, std), ...parsed]
180
182
  if (options.compile) {
181
- const js = Object.values(comp(deepClone(ast))).join('')
182
- return options.eval ? eval(js) : js
183
+ switch (options.compile) {
184
+ case 1: {
185
+ const js = Object.values(JavaScript(deepClone(ast))).join('')
186
+ return options.eval ? eval(js) : js
187
+ }
188
+ case 2: {
189
+ const oCaml = Object.values(OCaml(deepClone(ast))).join('')
190
+ return oCaml
191
+ }
192
+ }
183
193
  }
184
194
  return run(ast, env)
185
195
  } else if (Array.isArray(source)) {
@@ -187,8 +197,16 @@ export const fez = (source, options = {}) => {
187
197
  ? AST.parse(AST.stringify(source).replace(new RegExp(/!/g), 'ǃ'))
188
198
  : source
189
199
  if (options.compile) {
190
- const js = Object.values(comp(deepClone(ast))).join('')
191
- return options.eval ? eval(js) : js
200
+ switch (options.compile) {
201
+ case 1: {
202
+ const js = Object.values(JavaScript(deepClone(ast))).join('')
203
+ return options.eval ? eval(js) : js
204
+ }
205
+ case 2: {
206
+ const OCaml = Object.values(OCaml(deepClone(ast))).join('')
207
+ return OCaml
208
+ }
209
+ }
192
210
  }
193
211
  return run(ast, env)
194
212
  } else {