fez-lisp 1.1.9 → 1.1.10

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/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 {