fez-lisp 1.3.1 → 1.3.2

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.3.1",
5
+ "version": "1.3.2",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "keywords": [
@@ -29,6 +29,5 @@
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
31
  "mocha": "^10.2.0"
32
- },
33
- "dependencies": {}
32
+ }
34
33
  }
package/src/compiler.js CHANGED
@@ -5,9 +5,9 @@ import {
5
5
  KEYWORDS,
6
6
  TYPE,
7
7
  VALUE,
8
- WORD,
9
- SUGGAR
8
+ WORD
10
9
  } from './keywords.js'
10
+ import { SUGGAR } from './macros.js'
11
11
  import { leaf, isLeaf, AST } from './parser.js'
12
12
  const deepRename = (name, newName, tree) => {
13
13
  if (!isLeaf(tree))
@@ -58,6 +58,20 @@ const keywordToHelper = (name) => {
58
58
  return '__lt'
59
59
  case KEYWORDS.LESS_THAN_OR_EQUAL:
60
60
  return '__lteq'
61
+ case KEYWORDS.BITWISE_AND:
62
+ return '__bit_and'
63
+ case KEYWORDS.BITWISE_OR:
64
+ return '__bit_or'
65
+ case KEYWORDS.BITWISE_XOR:
66
+ return '__bit_xor'
67
+ case KEYWORDS.BITWISE_NOT:
68
+ return '__bit_not'
69
+ case KEYWORDS.BITWISE_LEFT_SHIFT:
70
+ return '__bit_lshift'
71
+ case KEYWORDS.BITWISE_RIGHT_SHIFT:
72
+ return '__bit_rshift'
73
+ case KEYWORDS.BITWISE_UNSIGNED_RIGHT_SHIFT:
74
+ return '__bit_urshift'
61
75
  default:
62
76
  return name
63
77
  }
@@ -78,12 +92,19 @@ const Helpers = {
78
92
  __add: `__add=(a,b)=>{return a+b}`,
79
93
  __sub: `__sub=function(a,b){return arguments.length===1?-a:a-b}`,
80
94
  __mult: `__mult=(a,b)=>{return a*b}`,
81
- __div: `__div=function(a,b){return arguments.length===1?1/a:a/b}`,
95
+ __div: `__div=(a,b)=>{return a/b}`,
82
96
  __gteq: '__gteq=(a,b)=>+(a>=b)',
83
97
  __gt: '__gt=(a,b)=>+(a>b)',
84
98
  __eq: '__eq=(a,b)=>+(a===b)',
85
99
  __lteq: '__lteq=(a,b)=>+(a<=b)',
86
100
  __lt: '__lt=(a,b)=>+(a<b)',
101
+ __bit_and: '__bit_and=(a,b)=>a&b',
102
+ __bit_or: '__bit_or=(a,b)=>a|b',
103
+ __bit_xor: '__bit_xor=(a,b)=>a^b',
104
+ __bit_not: '__bit_not=(a)=>~a',
105
+ __bit_lshift: '__bit_lshift=(a,b)=>a<<b',
106
+ __bit_rshift: '__bit_rshift=(a,b)=>a>>b',
107
+ __bit_urshift: '__bit_urshift=(a,b)=>a>>>b',
87
108
  array: 'array=(...args)=>args',
88
109
  not: 'not=(a)=>+!a',
89
110
  and: `and=(a, b)=>+(a&&b)`,
@@ -262,9 +283,7 @@ const compile = (tree, Drill) => {
262
283
  case KEYWORDS.MULTIPLICATION:
263
284
  return `(${parseArgs(Arguments, Drill, token)});`
264
285
  case KEYWORDS.DIVISION:
265
- return Arguments.length === 1
266
- ? `(1/${compile(Arguments[0], Drill)});`
267
- : `(${parseArgs(Arguments, Drill, token)});`
286
+ return `(${parseArgs(Arguments, Drill, token)});`
268
287
  case KEYWORDS.ADDITION:
269
288
  return `(${parseArgs(Arguments, Drill, token)});`
270
289
  case KEYWORDS.BITWISE_AND:
@@ -3,11 +3,11 @@ import { evaluate } from './evaluator.js'
3
3
  import { isForbiddenVariableName, stringifyArgs } from './utils.js'
4
4
  export const keywords = {
5
5
  [KEYWORDS.REMAINDER_OF_DIVISION]: (args, env) => {
6
- if (args.length < 2)
6
+ if (args.length !== 2)
7
7
  throw new RangeError(
8
8
  `Invalid number of arguments for (${
9
9
  KEYWORDS.REMAINDER_OF_DIVISION
10
- }), expected > 1 but got ${args.length}. (${
10
+ }), expected (= 2) but got ${args.length}. (${
11
11
  KEYWORDS.REMAINDER_OF_DIVISION
12
12
  } ${stringifyArgs(args)})`
13
13
  )
@@ -37,24 +37,10 @@ export const keywords = {
37
37
  return a % b
38
38
  },
39
39
  [KEYWORDS.DIVISION]: (args, env) => {
40
- if (args.length === 1) {
41
- const number = evaluate(args[0], env)
42
- if (typeof number !== 'number')
43
- throw new TypeError(
44
- `Arguments of (${KEYWORDS.DIVISION}) is not a (${
45
- KEYWORDS.NUMBER_TYPE
46
- }) (${KEYWORDS.DIVISION} ${stringifyArgs(args)})`
47
- )
48
- if (number === 0)
49
- throw new TypeError(
50
- `Argument of (${
51
- KEYWORDS.DIVISION
52
- }) can't be a (0) (division by 0 is not allowed) (${
53
- KEYWORDS.DIVISION
54
- } ${stringifyArgs(args)})`
55
- )
56
- return 1 / number
57
- }
40
+ if (args.length !== 2)
41
+ throw new RangeError(
42
+ `Invalid number of arguments for (${KEYWORDS.DIVISION}), expected (= 2) but got ${args.length}.`
43
+ )
58
44
  const a = evaluate(args[0], env)
59
45
  if (typeof a !== 'number')
60
46
  throw new TypeError(
@@ -139,9 +125,9 @@ export const keywords = {
139
125
  return a + b
140
126
  },
141
127
  [KEYWORDS.MULTIPLICATION]: (args, env) => {
142
- if (args.length !== 0 && args.length !== 2)
128
+ if (args.length !== 2)
143
129
  throw new RangeError(
144
- `Invalid number of arguments for (${KEYWORDS.MULTIPLICATION}), expected (or (= 2) (= 0)) but got ${args.length}.`
130
+ `Invalid number of arguments for (${KEYWORDS.MULTIPLICATION}), expected (= 2) but got ${args.length}.`
145
131
  )
146
132
  const a = evaluate(args[0], env)
147
133
  if (typeof a !== 'number')
package/src/keywords.js CHANGED
@@ -6,19 +6,6 @@ export const ATOM = 2
6
6
  export const TRUE = 1
7
7
  export const FALSE = 0
8
8
  export const PLACEHOLDER = '.'
9
- export const SUGGAR = {
10
- // Syntactic suggars
11
- PIPE: '|>',
12
- NOT_EQUAL_1: '!=',
13
- NOT_EQUAL_2: '<>',
14
- UNLESS: 'unless',
15
- LIST_TYPE: 'list',
16
- POWER: '**',
17
- INTEGER_DEVISION: '//',
18
- CONDITION: 'cond',
19
- RECURSION: 'recursive',
20
- CACHE: 'memoized'
21
- }
22
9
  export const KEYWORDS = {
23
10
  NUMBER_TYPE: 'number',
24
11
  ARRAY_TYPE: 'array',
package/src/macros.js ADDED
@@ -0,0 +1,408 @@
1
+ import { isLeaf } from './parser.js'
2
+ import {
3
+ EXPONENTIATION,
4
+ INTEGER_DIVISION,
5
+ NOT_EQUAL
6
+ } from '../lib/baked/macros.js'
7
+ import {
8
+ APPLY,
9
+ ATOM,
10
+ FALSE,
11
+ KEYWORDS,
12
+ TRUE,
13
+ TYPE,
14
+ VALUE,
15
+ WORD
16
+ } from './keywords.js'
17
+ import { stringifyArgs } from './utils.js'
18
+ export const SUGGAR = {
19
+ // Syntactic suggars
20
+ PIPE: '|>',
21
+ NOT_EQUAL_1: '!=',
22
+ NOT_EQUAL_2: '<>',
23
+ REMAINDER_OF_DIVISION_1: '%',
24
+ UNLESS: 'unless',
25
+ LIST_TYPE: 'list',
26
+ POWER: '**',
27
+ INTEGER_DEVISION: '//',
28
+ CONDITION: 'cond',
29
+ RECURSION: 'recursive',
30
+ CACHE: 'memoized'
31
+ }
32
+ export const deSuggarAst = (ast) => {
33
+ if (ast.length === 0) throw new SyntaxError(`No expressions to evaluate`)
34
+ // for (const node of ast)
35
+ // if (node[0] && node[0][TYPE] === APPLY && node[0][VALUE] === KEYWORDS.BLOCK)
36
+ // throw new SyntaxError(`Top level (${KEYWORDS.BLOCK}) is not allowed`)
37
+ let prev = undefined
38
+ const evaluate = (exp) => {
39
+ const [first, ...rest] = isLeaf(exp) ? [exp] : exp
40
+ if (first != undefined) {
41
+ switch (first[TYPE]) {
42
+ case WORD:
43
+ {
44
+ switch (first[VALUE]) {
45
+ case SUGGAR.REMAINDER_OF_DIVISION_1:
46
+ exp[VALUE] = KEYWORDS.REMAINDER_OF_DIVISION
47
+ break
48
+ case SUGGAR.NOT_EQUAL_1:
49
+ case SUGGAR.NOT_EQUAL_2:
50
+ exp.length = 0
51
+ exp.push(...NOT_EQUAL)
52
+ break
53
+ case SUGGAR.POWER:
54
+ exp.length = 0
55
+ exp.push(...EXPONENTIATION)
56
+ break
57
+ case SUGGAR.INTEGER_DEVISION:
58
+ exp.length = 0
59
+ exp.push(...INTEGER_DIVISION)
60
+ break
61
+ }
62
+ }
63
+ break
64
+ case ATOM:
65
+ break
66
+ case APPLY:
67
+ {
68
+ switch (first[VALUE]) {
69
+ case KEYWORDS.BLOCK:
70
+ {
71
+ if (
72
+ prev == undefined ||
73
+ (prev &&
74
+ prev[TYPE] === APPLY &&
75
+ prev[VALUE] !== KEYWORDS.ANONYMOUS_FUNCTION)
76
+ ) {
77
+ exp[0][VALUE] = KEYWORDS.CALL_FUNCTION
78
+ exp[0][TYPE] = APPLY
79
+ exp.length = 1
80
+ exp[1] = [
81
+ [APPLY, KEYWORDS.ANONYMOUS_FUNCTION],
82
+ [[APPLY, KEYWORDS.BLOCK], ...rest]
83
+ ]
84
+ deSuggarAst(exp)
85
+ }
86
+ }
87
+ break
88
+ case SUGGAR.PIPE:
89
+ {
90
+ if (rest.length < 1)
91
+ throw new RangeError(
92
+ `Invalid number of arguments to (${
93
+ SUGGAR.PIPE
94
+ }) (>= 1 required). (${SUGGAR.PIPE} ${stringifyArgs(
95
+ rest
96
+ )})`
97
+ )
98
+ let inp = rest[0]
99
+ exp.length = 0
100
+ for (let i = 1; i < rest.length; ++i) {
101
+ if (!rest[i].length || rest[i][0][TYPE] !== APPLY)
102
+ throw new TypeError(
103
+ `Argument at position (${i}) of (${
104
+ SUGGAR.PIPE
105
+ }) is not an invoked (${
106
+ KEYWORDS.ANONYMOUS_FUNCTION
107
+ }). (${SUGGAR.PIPE} ${stringifyArgs(rest)})`
108
+ )
109
+ inp = [rest[i].shift(), inp, ...rest[i]]
110
+ }
111
+ for (let i = 0; i < inp.length; ++i) exp[i] = inp[i]
112
+ deSuggarAst(exp)
113
+ }
114
+ break
115
+ case SUGGAR.CONDITION:
116
+ {
117
+ if (rest.length < 2)
118
+ throw new RangeError(
119
+ `Invalid number of arguments for (${
120
+ SUGGAR.CONDITION
121
+ }), expected (> 2 required) but got ${rest.length} (${
122
+ SUGGAR.CONDITION
123
+ } ${stringifyArgs(rest)})`
124
+ )
125
+ if (rest.length % 2 !== 0)
126
+ throw new RangeError(
127
+ `Invalid number of arguments for (${
128
+ SUGGAR.CONDITION
129
+ }), expected even number of arguments but got ${
130
+ rest.length
131
+ } (${SUGGAR.CONDITION} ${stringifyArgs(rest)})`
132
+ )
133
+ exp.length = 0
134
+ let temp = exp
135
+ for (let i = 0; i < rest.length; i += 2) {
136
+ if (i === rest.length - 2) {
137
+ temp.push([APPLY, KEYWORDS.IF], rest[i], rest.at(-1))
138
+ } else {
139
+ temp.push([APPLY, KEYWORDS.IF], rest[i], rest[i + 1], [])
140
+ temp = temp.at(-1)
141
+ }
142
+ }
143
+ deSuggarAst(exp)
144
+ }
145
+ break
146
+ case SUGGAR.LIST_TYPE:
147
+ {
148
+ exp.length = 0
149
+ let temp = exp
150
+ for (const item of rest) {
151
+ temp.push([APPLY, KEYWORDS.ARRAY_TYPE], item, [])
152
+ temp = temp.at(-1)
153
+ }
154
+ temp.push([APPLY, KEYWORDS.ARRAY_TYPE])
155
+ }
156
+ deSuggarAst(exp)
157
+ break
158
+ case SUGGAR.INTEGER_DEVISION:
159
+ {
160
+ if (rest.length !== 2)
161
+ throw new RangeError(
162
+ `Invalid number of arguments for (${
163
+ SUGGAR.INTEGER_DEVISION
164
+ }), expected (= 2) but got ${rest.length} (${
165
+ SUGGAR.INTEGER_DEVISION
166
+ } ${stringifyArgs(rest)})`
167
+ )
168
+ else if (rest.some((x) => x[TYPE] === APPLY)) {
169
+ exp.length = 0
170
+ exp.push(
171
+ [0, KEYWORDS.CALL_FUNCTION],
172
+ INTEGER_DIVISION,
173
+ ...rest
174
+ )
175
+ } else {
176
+ exp.length = 1
177
+ exp[0] = [APPLY, KEYWORDS.BITWISE_OR]
178
+ exp.push([[APPLY, KEYWORDS.DIVISION], ...rest])
179
+ exp.push([ATOM, 0])
180
+ }
181
+ }
182
+ break
183
+ case SUGGAR.POWER:
184
+ {
185
+ if (rest.length !== 2)
186
+ throw new RangeError(
187
+ `Invalid number of arguments for (${
188
+ SUGGAR.POWER
189
+ }), expected (= 2) but got ${rest.length} (${
190
+ SUGGAR.POWER
191
+ } ${stringifyArgs(rest)})`
192
+ )
193
+ const isExponentAtom = exp[1][TYPE] === ATOM
194
+ const isPowerAtom = exp[2][TYPE] === ATOM
195
+ const isExponentWord = exp[1][TYPE] === WORD
196
+ if ((isExponentWord || isExponentAtom) && isPowerAtom) {
197
+ if (isExponentAtom) {
198
+ exp[0][VALUE] = KEYWORDS.MULTIPLICATION
199
+ const exponent = exp[1]
200
+ const power = exp[2][VALUE]
201
+ exp.length = 1
202
+ exp.push(exponent, [ATOM, exponent[VALUE] ** (power - 1)])
203
+ } else if (isExponentWord) {
204
+ const exponent = exp[1]
205
+ const power = exp[2]
206
+ exp.length = 0
207
+ exp.push(
208
+ [0, KEYWORDS.CALL_FUNCTION],
209
+ EXPONENTIATION,
210
+ exponent,
211
+ power
212
+ )
213
+ }
214
+ } else {
215
+ const exponent = exp[1]
216
+ const power = exp[2]
217
+ exp.length = 0
218
+ exp.push(
219
+ [0, KEYWORDS.CALL_FUNCTION],
220
+ EXPONENTIATION,
221
+ exponent,
222
+ power
223
+ )
224
+ }
225
+ deSuggarAst(exp)
226
+ }
227
+ break
228
+ case KEYWORDS.MULTIPLICATION:
229
+ if (!rest.length) {
230
+ exp[0][TYPE] = ATOM
231
+ exp[0][VALUE] = TRUE
232
+ } else if (rest.length > 2) {
233
+ exp.length = 0
234
+ let temp = exp
235
+ for (let i = 0; i < rest.length; i += 1) {
236
+ if (i < rest.length - 1) {
237
+ temp.push([APPLY, KEYWORDS.MULTIPLICATION], rest[i], [])
238
+ temp = temp.at(-1)
239
+ } else {
240
+ temp.push(...rest[i])
241
+ }
242
+ }
243
+ deSuggarAst(exp)
244
+ }
245
+ break
246
+ case KEYWORDS.ADDITION:
247
+ if (!rest.length) {
248
+ exp[0][TYPE] = ATOM
249
+ exp[0][VALUE] = FALSE
250
+ } else if (rest.length > 2) {
251
+ exp.length = 0
252
+ let temp = exp
253
+ for (let i = 0; i < rest.length; i += 1) {
254
+ if (i < rest.length - 1) {
255
+ temp.push([APPLY, KEYWORDS.ADDITION], rest[i], [])
256
+ temp = temp.at(-1)
257
+ } else {
258
+ temp.push(...rest[i])
259
+ }
260
+ }
261
+ deSuggarAst(exp)
262
+ }
263
+ break
264
+ case KEYWORDS.DIVISION:
265
+ if (!rest.length) {
266
+ exp[0][TYPE] = ATOM
267
+ exp[0][VALUE] = FALSE
268
+ } else if (rest.length === 1) {
269
+ exp.length = 1
270
+ exp.push([ATOM, 1], rest[0])
271
+ } else if (rest.length > 2) {
272
+ exp.length = 0
273
+ let temp = exp
274
+ for (let i = 0; i < rest.length; i += 1) {
275
+ if (i < rest.length - 1) {
276
+ temp.push([APPLY, KEYWORDS.DIVISION], rest[i], [])
277
+ temp = temp.at(-1)
278
+ } else {
279
+ temp.push(...rest[i])
280
+ }
281
+ }
282
+ deSuggarAst(exp)
283
+ }
284
+ break
285
+ case KEYWORDS.AND:
286
+ if (!rest.length) {
287
+ exp[0][TYPE] = ATOM
288
+ exp[0][VALUE] = FALSE
289
+ } else if (rest.length > 2) {
290
+ exp.length = 0
291
+ let temp = exp
292
+ for (let i = 0; i < rest.length; i += 1) {
293
+ if (i < rest.length - 1) {
294
+ temp.push([APPLY, KEYWORDS.AND], rest[i], [])
295
+ temp = temp.at(-1)
296
+ } else {
297
+ temp.push(...rest[i])
298
+ }
299
+ }
300
+ deSuggarAst(exp)
301
+ }
302
+ break
303
+ case KEYWORDS.OR:
304
+ if (!rest.length) {
305
+ exp[0][TYPE] = ATOM
306
+ exp[0][VALUE] = FALSE
307
+ } else if (rest.length > 2) {
308
+ exp.length = 0
309
+ let temp = exp
310
+ for (let i = 0; i < rest.length; i += 1) {
311
+ if (i < rest.length - 1) {
312
+ temp.push([APPLY, KEYWORDS.OR], rest[i], [])
313
+ temp = temp.at(-1)
314
+ } else {
315
+ temp.push(...rest[i])
316
+ }
317
+ }
318
+ deSuggarAst(exp)
319
+ }
320
+ break
321
+ case SUGGAR.UNLESS:
322
+ {
323
+ if (rest.length > 3 || rest.length < 2)
324
+ throw new RangeError(
325
+ `Invalid number of arguments for (${
326
+ SUGGAR.UNLESS
327
+ }), expected (or (= 3) (= 2)) but got ${rest.length} (${
328
+ SUGGAR.UNLESS
329
+ } ${stringifyArgs(rest)})`
330
+ )
331
+ exp[0][VALUE] = KEYWORDS.IF
332
+ const temp = exp[2]
333
+ exp[2] = exp[3] ?? [ATOM, FALSE]
334
+ exp[3] = temp
335
+ }
336
+ deSuggarAst(exp)
337
+ break
338
+ case SUGGAR.REMAINDER_OF_DIVISION_1:
339
+ {
340
+ if (rest.length !== 2)
341
+ throw new RangeError(
342
+ `Invalid number of arguments for (${
343
+ exp[0][1]
344
+ }), expected (= 2) but got ${rest.length} (${
345
+ exp[0][1]
346
+ } ${stringifyArgs(rest)})`
347
+ )
348
+ exp[0][VALUE] = KEYWORDS.REMAINDER_OF_DIVISION
349
+ deSuggarAst(exp)
350
+ }
351
+ break
352
+ case SUGGAR.NOT_EQUAL_1:
353
+ case SUGGAR.NOT_EQUAL_2:
354
+ {
355
+ if (rest.length !== 2)
356
+ throw new RangeError(
357
+ `Invalid number of arguments for (${
358
+ exp[0][1]
359
+ }), expected (= 2) but got ${rest.length} (${
360
+ exp[0][1]
361
+ } ${stringifyArgs(rest)})`
362
+ )
363
+ exp[0][VALUE] = KEYWORDS.NOT
364
+ exp[1] = [[APPLY, KEYWORDS.EQUAL], exp[1], exp[2]]
365
+ exp.length = 2
366
+ deSuggarAst(exp)
367
+ }
368
+ break
369
+ }
370
+ prev = first
371
+ }
372
+ break
373
+ default:
374
+ for (const e of exp) evaluate(e)
375
+ break
376
+ }
377
+ for (const r of rest) evaluate(r)
378
+ }
379
+ }
380
+ evaluate(ast)
381
+ return ast
382
+ }
383
+ export const replaceStrings = (source) => {
384
+ // const quotes = source.match(/"(.*?)"/g)
385
+ const quotes = source.match(/"(?:.*?(\n|\r))*?.*?"/g)
386
+ // TODO handle escaping
387
+ if (quotes)
388
+ for (const q of quotes)
389
+ source = source.replaceAll(
390
+ q,
391
+ `(${KEYWORDS.ARRAY_TYPE} ${[...q.replaceAll('\r', '')]
392
+ .slice(1, -1)
393
+ .map((x) => x.charCodeAt(0))
394
+ .join(' ')})`
395
+ )
396
+ return source
397
+ }
398
+ export const replaceQuotes = (source) =>
399
+ source
400
+ .replaceAll(/\'\(/g, `(${KEYWORDS.ARRAY_TYPE} `)
401
+ .replaceAll(/\`\(/g, `(${SUGGAR.LIST_TYPE} `)
402
+ .replaceAll(/\(\)/g, `(${KEYWORDS.ARRAY_TYPE})`)
403
+ export const deSuggarSource = (source) => replaceQuotes(replaceStrings(source))
404
+ export const handleUnbalancedQuotes = (source) => {
405
+ const diff = (source.match(/\"/g) ?? []).length % 2
406
+ if (diff !== 0) throw new SyntaxError(`Quotes are unbalanced "`)
407
+ return source
408
+ }