fez-lisp 1.0.54 → 1.1.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.0.54",
5
+ "version": "1.1.2",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "keywords": [
package/src/compiler.js CHANGED
@@ -8,8 +8,65 @@ import {
8
8
  WORD
9
9
  } from './keywords.js'
10
10
  import { leaf, isLeaf } from './parser.js'
11
- import { deepRename, lispToJavaScriptVariableName } from './utils.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
+ )
12
68
  const Helpers = {
69
+ __string: `__string=(...args)=>{const str=args.flat();str.isString=true;return str}`,
13
70
  __add: `__add=(...numbers)=>{return numbers.reduce((a,b)=>a+b,0)}`,
14
71
  __sub: `__sub=(...numbers)=>{return numbers.reduce((a,b)=>a-b,0)}`,
15
72
  __mult: `__mult=(...numbers)=>{return numbers.reduce((a,b)=>a*b,1)}`,
@@ -46,13 +103,10 @@ const Helpers = {
46
103
  length: 'length=(arr)=>arr.length',
47
104
  __tco: `__tco=fn=>(...args)=>{let result=fn(...args);while(typeof result==='function')result=result();return result}`,
48
105
  numberPredicate: `numberPredicate=(number)=>+(typeof number==='number')`,
49
- stringPredicate: `stringPredicate=(string)=>+(typeof string==='string')`,
50
106
  lambdaPredicate: `lambdaPredicate=(lambda)=>+(typeof lambda==='function')`,
51
107
  arrayPredicate: `arrayPredicate=(array)=>+Array.isArray(array)`,
52
- atomPredicate: `atomPredicate=(value)=>+(typeof value==='number'||typeof value==='string')`,
53
108
  error: `error=(error)=>{throw new Error(error)}`,
54
- 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}`,
55
- cast: `cast=(type,value)=>{switch(type){case '${KEYWORDS.NUMBER_TYPE}':return Number(value);case '${KEYWORDS.STRING_TYPE}':return value.toString();case '${KEYWORDS.ARRAY_TYPE}':return typeof value==='number'?[...Number(value).toString()].map(Number):[...value];case '${KEYWORDS.BOOLEAN_TYPE}':return +!!value;case '${KEYWORDS.ANONYMOUS_FUNCTION}':return ()=>value;case '${KEYWORDS.CHAR_CODE_TYPE}':return value.charCodeAt(0);case '${KEYWORDS.CHAR_TYPE}':return String.fromCharCode(value);default:return 0}}`
109
+ 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}`
56
110
  }
57
111
  const semiColumnEdgeCases = new Set([
58
112
  ';)',
@@ -116,9 +170,6 @@ const compile = (tree, Drill) => {
116
170
  out += `),${name});`
117
171
  return out
118
172
  }
119
- case KEYWORDS.IS_STRING:
120
- Drill.Helpers.add('stringPredicate')
121
- return `stringPredicate(${compile(Arguments[0], Drill)});`
122
173
  case KEYWORDS.IS_NUMBER:
123
174
  Drill.Helpers.add('numberPredicate')
124
175
  return `numberPredicate(${compile(Arguments[0], Drill)});`
@@ -133,19 +184,17 @@ const compile = (tree, Drill) => {
133
184
  case KEYWORDS.BOOLEAN_TYPE:
134
185
  return '1'
135
186
  case KEYWORDS.STRING_TYPE:
136
- return '""'
187
+ Drill.Helpers.add('__string')
188
+ return `__string(${parseArgs(Arguments, Drill)});`
137
189
  case KEYWORDS.ARRAY_TYPE:
138
190
  return Arguments.length === 2 &&
139
191
  Arguments[1][TYPE] === WORD &&
140
192
  Arguments[1][VALUE] === 'length'
141
- ? `(new Array(${compile(Arguments[0], Drill)}).fill(0))`
193
+ ? `(new Array(${compile(Arguments[0], Drill)}).fill(0));`
142
194
  : `[${parseArgs(Arguments, Drill)}];`
143
- case KEYWORDS.ARRAY_OR_STRING_LENGTH:
195
+ case KEYWORDS.ARRAY_LENGTH:
144
196
  Drill.Helpers.add('length')
145
197
  return `length(${compile(Arguments[0], Drill)})`
146
- case KEYWORDS.IS_ATOM:
147
- Drill.Helpers.add('atomPredicate')
148
- return `atomPredicate(${compile(Arguments[0], Drill)});`
149
198
  case KEYWORDS.FIRST_ARRAY:
150
199
  Drill.Helpers.add('car')
151
200
  return `car(${compile(Arguments[0], Drill)});`
@@ -295,9 +344,6 @@ const compile = (tree, Drill) => {
295
344
  out += '0);'
296
345
  return out
297
346
  }
298
- case KEYWORDS.CAST_TYPE:
299
- Drill.Helpers.add('cast')
300
- return `cast("${Arguments[1][VALUE]}", ${compile(Arguments[0], Drill)})`
301
347
  case KEYWORDS.PIPE: {
302
348
  let inp = Arguments[0]
303
349
  for (let i = 1; i < Arguments.length; ++i)
@@ -319,10 +365,7 @@ const compile = (tree, Drill) => {
319
365
  return `${camelCased}(${parseArgs(Arguments, Drill)});`
320
366
  }
321
367
  }
322
- } else if (first[TYPE] === ATOM)
323
- return typeof first[VALUE] === 'string'
324
- ? `\`${first[VALUE]}\``
325
- : first[VALUE]
368
+ } else if (first[TYPE] === ATOM) return first[VALUE]
326
369
  else if (first[TYPE] === WORD) {
327
370
  const camelCased = lispToJavaScriptVariableName(token)
328
371
  if (camelCased in Helpers) Drill.Helpers.add(camelCased)
package/src/evaluator.js CHANGED
@@ -36,7 +36,7 @@ export const isAtom = (arg, env) => {
36
36
  if (arg[TYPE] === ATOM) return 1
37
37
  else {
38
38
  const atom = evaluate(arg, env)
39
- return +(typeof atom === 'number' || typeof atom === 'string')
39
+ return +(typeof atom === 'number')
40
40
  }
41
41
  }
42
42
  export const run = (tree, env = {}) =>
@@ -8,26 +8,7 @@ import {
8
8
  isForbiddenVariableName,
9
9
  stringifyArgs
10
10
  } from './utils.js'
11
-
12
11
  const keywords = {
13
- [KEYWORDS.CONCATENATION]: (args, env) => {
14
- if (args.length < 2)
15
- throw new RangeError(
16
- `Invalid number of arguments for (${
17
- KEYWORDS.CONCATENATION
18
- }), expected > 1 but got ${args.length}. (${
19
- KEYWORDS.CONCATENATION
20
- } ${stringifyArgs(args)})`
21
- )
22
- const operands = args.map((x) => evaluate(x, env))
23
- if (operands.some((x) => typeof x !== 'string'))
24
- throw new TypeError(
25
- `Not all arguments of (${KEYWORDS.CONCATENATION}) are (${
26
- KEYWORDS.STRING_TYPE
27
- }) (${KEYWORDS.CONCATENATION} ${stringifyArgs(args)})`
28
- )
29
- return operands.reduce((a, b) => a + b, '')
30
- },
31
12
  [KEYWORDS.REMAINDER_OF_DIVISION]: (args, env) => {
32
13
  if (args.length < 2)
33
14
  throw new RangeError(
@@ -92,23 +73,19 @@ const keywords = {
92
73
  )
93
74
  return operands.reduce((a, b) => a / b)
94
75
  },
95
- [KEYWORDS.ARRAY_OR_STRING_LENGTH]: (args, env) => {
76
+ [KEYWORDS.ARRAY_LENGTH]: (args, env) => {
96
77
  if (args.length !== 1)
97
78
  throw new RangeError(
98
79
  `Invalid number of arguments for (${
99
- KEYWORDS.ARRAY_OR_STRING_LENGTH
100
- }) (= 1 required) (${KEYWORDS.ARRAY_OR_STRING_LENGTH} ${stringifyArgs(
101
- args
102
- )})`
80
+ KEYWORDS.ARRAY_LENGTH
81
+ }) (= 1 required) (${KEYWORDS.ARRAY_LENGTH} ${stringifyArgs(args)})`
103
82
  )
104
83
  const array = evaluate(args[0], env)
105
- if (!(Array.isArray(array) || typeof array === 'string'))
84
+ if (!Array.isArray(array))
106
85
  throw new TypeError(
107
- `First argument of (${
108
- KEYWORDS.ARRAY_OR_STRING_LENGTH
109
- }) must be an (or ${KEYWORDS.ARRAY_TYPE} ${KEYWORDS.STRING_TYPE}) (${
110
- KEYWORDS.ARRAY_OR_STRING_LENGTH
111
- } ${stringifyArgs(args)})`
86
+ `First argument of (${KEYWORDS.ARRAY_LENGTH}) must be a ${
87
+ KEYWORDS.ARRAY_TYPE
88
+ } (${KEYWORDS.ARRAY_LENGTH} ${stringifyArgs(args)})`
112
89
  )
113
90
  return array.length
114
91
  },
@@ -131,15 +108,6 @@ const keywords = {
131
108
  )
132
109
  return +(typeof evaluate(args[0], env) === 'number')
133
110
  },
134
- [KEYWORDS.IS_STRING]: (args, env) => {
135
- if (args.length !== 1)
136
- throw new RangeError(
137
- `Invalid number of arguments for (${
138
- KEYWORDS.IS_STRING
139
- }) (= 1 required) (${KEYWORDS.IS_STRING} ${stringifyArgs(args)})`
140
- )
141
- return +(typeof evaluate(args[0], env) === 'string')
142
- },
143
111
  [KEYWORDS.IS_FUNCTION]: (args, env) => {
144
112
  if (args.length !== 1)
145
113
  throw new RangeError(
@@ -262,6 +230,11 @@ const keywords = {
262
230
  if (evaluate(args[i], env)) return evaluate(args[i + 1], env)
263
231
  return 0
264
232
  },
233
+ [KEYWORDS.STRING_TYPE]: (args, env) => {
234
+ const str = args.flatMap((x) => evaluate(x, env))
235
+ str.isString = true
236
+ return str
237
+ },
265
238
  [KEYWORDS.ARRAY_TYPE]: (args, env) => {
266
239
  if (!args.length) return []
267
240
  const isCapacity =
@@ -284,15 +257,6 @@ const keywords = {
284
257
  }
285
258
  return args.map((x) => evaluate(x, env))
286
259
  },
287
- [KEYWORDS.IS_ATOM]: (args, env) => {
288
- if (args.length !== 1)
289
- throw new RangeError(
290
- `Invalid number of arguments for (${
291
- KEYWORDS.IS_ATOM
292
- }) (= 1 required) (${KEYWORDS.IS_ATOM} ${stringifyArgs(args)})`
293
- )
294
- return isAtom(args[0], env)
295
- },
296
260
  [KEYWORDS.FIRST_ARRAY]: (args, env) => {
297
261
  if (args.length !== 1)
298
262
  throw new RangeError(
@@ -439,7 +403,7 @@ const keywords = {
439
403
  const b = evaluate(args[1], env)
440
404
  if (typeof a !== 'number')
441
405
  throw new TypeError(
442
- `Invalid use of (${KEYWORDS.EQUAL}), first arguments are not an ${
406
+ `Invalid use of (${KEYWORDS.EQUAL}), first argument is not an ${
443
407
  KEYWORDS.NUMBER_TYPE
444
408
  } (${KEYWORDS.EQUAL} ${stringifyArgs(args)})`
445
409
  )
@@ -462,7 +426,7 @@ const keywords = {
462
426
  const b = evaluate(args[1], env)
463
427
  if (typeof a !== 'number')
464
428
  throw new TypeError(
465
- `Invalid use of (${KEYWORDS.LESS_THAN}), first arguments are not an ${
429
+ `Invalid use of (${KEYWORDS.LESS_THAN}), first argument is not an ${
466
430
  KEYWORDS.NUMBER_TYPE
467
431
  } (${KEYWORDS.LESS_THAN} ${stringifyArgs(args)})`
468
432
  )
@@ -485,11 +449,9 @@ const keywords = {
485
449
  const b = evaluate(args[1], env)
486
450
  if (typeof a !== 'number')
487
451
  throw new TypeError(
488
- `Invalid use of (${
489
- KEYWORDS.GREATHER_THAN
490
- }), first arguments are not an ${KEYWORDS.NUMBER_TYPE} (${
491
- KEYWORDS.GREATHER_THAN
492
- } ${stringifyArgs(args)})`
452
+ `Invalid use of (${KEYWORDS.GREATHER_THAN}), first argument is not an ${
453
+ KEYWORDS.NUMBER_TYPE
454
+ } (${KEYWORDS.GREATHER_THAN} ${stringifyArgs(args)})`
493
455
  )
494
456
  if (typeof b !== 'number')
495
457
  throw new TypeError(
@@ -516,7 +478,7 @@ const keywords = {
516
478
  throw new TypeError(
517
479
  `Invalid use of (${
518
480
  KEYWORDS.GREATHER_THAN_OR_EQUAL
519
- }), first arguments are not an ${KEYWORDS.NUMBER_TYPE} (${
481
+ }), first argument is not an ${KEYWORDS.NUMBER_TYPE} (${
520
482
  KEYWORDS.GREATHER_THAN_OR_EQUAL
521
483
  } ${stringifyArgs(args)})`
522
484
  )
@@ -545,7 +507,7 @@ const keywords = {
545
507
  throw new TypeError(
546
508
  `Invalid use of (${
547
509
  KEYWORDS.LESS_THAN_OR_EQUAL
548
- }), first arguments are not an ${KEYWORDS.NUMBER_TYPE} (${
510
+ }), first argument is not an ${KEYWORDS.NUMBER_TYPE} (${
549
511
  KEYWORDS.LESS_THAN_OR_EQUAL
550
512
  } ${stringifyArgs(args)})`
551
513
  )
@@ -643,7 +605,6 @@ const keywords = {
643
605
  })
644
606
  return env[name]
645
607
  },
646
- [KEYWORDS.STRING_TYPE]: () => '',
647
608
  [KEYWORDS.NUMBER_TYPE]: () => 0,
648
609
  [KEYWORDS.BOOLEAN_TYPE]: () => 1,
649
610
  [KEYWORDS.CAST_TYPE]: (args, env) => {
@@ -671,60 +632,13 @@ const keywords = {
671
632
  )
672
633
  return num
673
634
  }
674
- case KEYWORDS.STRING_TYPE:
675
- return value.toString()
676
635
  case KEYWORDS.BOOLEAN_TYPE:
677
636
  return +!!value
678
- case KEYWORDS.ARRAY_TYPE: {
679
- if (typeof value === 'number')
680
- return [...Number(value).toString()].map(Number)
681
- else if (typeof value[Symbol.iterator] !== 'function')
682
- throw new TypeError(
683
- `Arguments are not iterable for ${KEYWORDS.ARRAY_TYPE} at (${
684
- KEYWORDS.CAST_TYPE
685
- }) (${KEYWORDS.CAST_TYPE} ${stringifyArgs(args)})`
686
- )
687
- return [...value]
688
- }
689
- case KEYWORDS.CHAR_TYPE: {
690
- const index = evaluate(args[0], env)
691
- if (!Number.isInteger(index) || index < 0)
692
- throw new TypeError(
693
- `Arguments are not (+ ${KEYWORDS.NUMBER_TYPE}) for ${
694
- KEYWORDS.CHAR_TYPE
695
- } at (${KEYWORDS.CAST_TYPE}) (${
696
- KEYWORDS.CAST_TYPE
697
- } ${stringifyArgs(args)})`
698
- )
699
- return String.fromCharCode(index)
700
- }
701
- case KEYWORDS.CHAR_CODE_TYPE: {
702
- const string = evaluate(args[0], env)
703
- if (typeof string !== 'string')
704
- throw new TypeError(
705
- `Argument is not (${KEYWORDS.STRING_TYPE}) for ${
706
- KEYWORDS.CHAR_CODE_TYPE
707
- } at (${KEYWORDS.CAST_TYPE}) (${
708
- KEYWORDS.CAST_TYPE
709
- } ${stringifyArgs(args)})`
710
- )
711
- if (string.length !== 1)
712
- throw new RangeError(
713
- `Argument is not of (= (length ${KEYWORDS.STRING_TYPE}) 1) for ${
714
- KEYWORDS.CHAR_CODE_TYPE
715
- } at (${KEYWORDS.CAST_TYPE}) (${
716
- KEYWORDS.CAST_TYPE
717
- } ${stringifyArgs(args)})`
718
- )
719
- return string.charCodeAt(0)
720
- }
721
637
  default:
722
638
  throw new TypeError(
723
- `Can only cast (or ${KEYWORDS.NUMBER_TYPE} ${
724
- KEYWORDS.STRING_TYPE
725
- } ${KEYWORDS.ARRAY_TYPE} ${KEYWORDS.BOOLEAN_TYPE} ${
726
- KEYWORDS.CHAR_TYPE
727
- } ${KEYWORDS.CHAR_CODE_TYPE}) at (${KEYWORDS.CAST_TYPE}) (${
639
+ `Can only cast (or ${KEYWORDS.NUMBER_TYPE} ${KEYWORDS.ARRAY_TYPE} ${
640
+ KEYWORDS.BOOLEAN_TYPE
641
+ }) at (${KEYWORDS.CAST_TYPE}) (${
728
642
  KEYWORDS.CAST_TYPE
729
643
  } ${stringifyArgs(args)})`
730
644
  )
@@ -965,18 +879,17 @@ const keywords = {
965
879
  return keywords[KEYWORDS.DEFINE_VARIABLE](args, env)
966
880
  },
967
881
  [KEYWORDS.TEST_CASE]: (args, env) => {
968
- if (args.length !== 3)
882
+ if (args.length !== 2)
969
883
  throw new RangeError(
970
884
  `Invalid number of arguments to (${
971
885
  KEYWORDS.TEST_CASE
972
- }) (= 3 required) (${KEYWORDS.TEST_CASE} ${stringifyArgs(args)})`
886
+ }) (= 2 required) (${KEYWORDS.TEST_CASE} ${stringifyArgs(args)})`
973
887
  )
974
- const description = evaluate(args[0], env)
975
- const a = evaluate(args[1], env)
976
- const b = evaluate(args[2], env)
888
+ const a = evaluate(args[0], env)
889
+ const b = evaluate(args[1], env)
977
890
  return !isEqualTypes(a, b) || !isEqual(a, b)
978
- ? [0, description, stringifyArgs([args[1]]), b, a]
979
- : [1, description, stringifyArgs([args[1]]), a]
891
+ ? [0, stringifyArgs([args[0]]), b, a]
892
+ : [1, stringifyArgs([args[0]]), a]
980
893
  },
981
894
  [KEYWORDS.TEST_BED]: (args, env) => {
982
895
  let tests = []
@@ -994,15 +907,13 @@ const keywords = {
994
907
  )
995
908
  tests = args.map((x) => evaluate(x, env))
996
909
  res = tests.reduce(
997
- (acc, [state, describe, ...rest]) =>
910
+ (acc, [state, ...rest]) =>
998
911
  `${acc}${
999
912
  !state
1000
- ? `x ${describe} Failed:\n ${rest[0]}\n + ${LISP.stringify(
913
+ ? `x ${rest[0]}\n + ${LISP.stringify(
1001
914
  rest[1]
1002
915
  )}\n - ${LISP.stringify(rest[2])}\n`
1003
- : `✓ ${describe} Passed:\n ${rest[0]}\n + ${LISP.stringify(
1004
- rest[1]
1005
- )}\n`
916
+ : `✓ ${rest[0]}\n + ${LISP.stringify(rest[1])}\n`
1006
917
  }`,
1007
918
  ''
1008
919
  )
package/src/keywords.js CHANGED
@@ -9,20 +9,13 @@ export const ATOM = 2
9
9
  export const PLACEHOLDER = '.'
10
10
  // keywords aliases
11
11
  export const KEYWORDS = {
12
- STRING_TYPE: 'string',
13
12
  NUMBER_TYPE: 'number',
14
- BOOLEAN_TYPE: 'boolean',
13
+ STRING_TYPE: 'string',
15
14
  ARRAY_TYPE: 'array',
16
- CHAR_CODE_TYPE: 'char-code',
17
- CHAR_TYPE: 'char',
18
- CAST_TYPE: 'type',
19
- CONCATENATION: 'string:merge',
20
- ARRAY_OR_STRING_LENGTH: 'length',
15
+ ARRAY_LENGTH: 'length',
21
16
  IS_ARRAY: 'array?',
22
17
  IS_NUMBER: 'number?',
23
- IS_STRING: 'string?',
24
18
  IS_FUNCTION: 'lambda?',
25
- IS_ATOM: 'atom?',
26
19
  ADDITION: '+',
27
20
  SUBTRACTION: '-',
28
21
  MULTIPLICATION: '*',
package/src/parser.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { APPLY, ATOM, TYPE, WORD, VALUE } from './keywords.js'
2
- import { escape, preserveEscape } from './utils.js'
3
2
  export const leaf = (type, value) => [type, value]
4
3
  export const isLeaf = ([car]) => car === APPLY || car === ATOM || car === WORD
5
4
  export const LISP = {
@@ -10,15 +9,6 @@ export const LISP = {
10
9
  acc = ''
11
10
  for (let i = 0; i < source.length; ++i) {
12
11
  const cursor = source[i]
13
- if (cursor === '"') {
14
- acc += '"'
15
- ++i
16
- while (source[i] !== '"') {
17
- if (source[i] === '\\') acc += escape(source[++i])
18
- else acc += source[i]
19
- ++i
20
- }
21
- }
22
12
  if (cursor === '(') {
23
13
  head.push([])
24
14
  stack.push(head)
@@ -28,8 +18,6 @@ export const LISP = {
28
18
  acc = ''
29
19
  if (token) {
30
20
  if (!head.length) head.push(leaf(APPLY, token))
31
- else if (token.match(/^"([^"]*)"/))
32
- head.push(leaf(ATOM, token.substring(1, token.length - 1)))
33
21
  else if (token.match(/^-?[0-9]\d*(\.\d+)?$/))
34
22
  head.push(leaf(ATOM, Number(token)))
35
23
  else head.push(leaf(WORD, token))
@@ -50,7 +38,6 @@ export const LISP = {
50
38
  return `(array ${array
51
39
  .map(([key, value]) => `("${key}" ${LISP.stringify(value)})`)
52
40
  .join(' ')})`
53
- else if (typeof array === 'string') return `"${array}"`
54
41
  else if (typeof array === 'function') return '()'
55
42
  else if (typeof array === 'boolean') return +array
56
43
  else return array
@@ -65,10 +52,7 @@ export const LISP = {
65
52
  out += first[VALUE]
66
53
  break
67
54
  case ATOM:
68
- out +=
69
- typeof first[VALUE] === 'string'
70
- ? `"${preserveEscape(first[VALUE])}"`
71
- : first[VALUE]
55
+ out += first[VALUE]
72
56
  break
73
57
  case APPLY:
74
58
  out += `(${first[VALUE]} ${rest.map(dfs).join(' ')})`
@@ -117,6 +101,6 @@ export const AST = {
117
101
  typeof ast === 'object'
118
102
  ? `[${ast.map(AST.stringify).join(',')}]`
119
103
  : typeof ast === 'string'
120
- ? `"${preserveEscape(ast)}"`
104
+ ? `"${ast}"`
121
105
  : ast
122
106
  }
package/src/utils.js CHANGED
@@ -5,16 +5,29 @@ import { run } from './evaluator.js'
5
5
  import { AST, isLeaf, LISP } from './parser.js'
6
6
  export const logError = (error) => console.log('\x1b[31m', error, '\x1b[0m')
7
7
  export const logSuccess = (output) => console.log(output, '\x1b[0m')
8
+ export const replaceStrings = (source) => {
9
+ const quotes = source.match(/"(.*?)"/g)
10
+ if (quotes)
11
+ for (const q of quotes)
12
+ source = source.replaceAll(
13
+ q,
14
+ `(string ${[...q]
15
+ .slice(1, -1)
16
+ .map((x) => x.charCodeAt(0))
17
+ .join(' ')})`
18
+ )
19
+ return source
20
+ }
8
21
  export const removeNoCode = (source) =>
9
22
  source
10
- // .replace(/;.+/g, '')
11
- .replace(/;(?=(?:(?:[^"]*"){2})*[^"]*$).+/g, '')
12
- .replace(/[\s\s]+(?=[^"]*(?:"[^"]*"[^"]*)*$)/g, ' ')
23
+ .replace(/;.+/g, '')
24
+ .replace(/[\s\s]/g, ' ')
13
25
  .trim()
26
+
14
27
  export const isBalancedParenthesis = (sourceCode) => {
15
28
  let count = 0
16
29
  const stack = []
17
- const str = sourceCode.match(/[/\(|\)](?=[^"]*(?:"[^"]*"[^"]*)*$)/g) ?? []
30
+ const str = sourceCode.match(/[/\(|\)]/g) ?? []
18
31
  for (let i = 0; i < str.length; ++i)
19
32
  if (str[i] === '(') stack.push(str[i])
20
33
  else if (str[i] === ')') if (stack.pop() !== '(') ++count
@@ -38,15 +51,6 @@ export const escape = (Char) => {
38
51
  return ''
39
52
  }
40
53
  }
41
- const escapeChars = {
42
- '\n': '\\n',
43
- '\r': '\\r',
44
- '\t': '\\t',
45
- s: '\\s',
46
- '"': '\\"'
47
- }
48
- export const preserveEscape = (str) =>
49
- str.replace(/[\n\r\t\s\"]/g, (match) => escapeChars[match] || match)
50
54
  export const stringifyType = (type) =>
51
55
  !isLeaf(type)
52
56
  ? `(array ${type.map((t) => stringifyType(t)).join(' ')})`
@@ -156,9 +160,10 @@ export const dfs = (tree, callback) => {
156
160
  }
157
161
  export const deepClone = (ast) => AST.parse(AST.stringify(ast))
158
162
  export const fez = (source, options = {}) => {
159
- const env = options.env ?? {}
163
+ const env = Object.create(null)
160
164
  try {
161
165
  if (typeof source === 'string') {
166
+ if (options.strings) source = replaceStrings(source)
162
167
  let code
163
168
  if (!options.compile)
164
169
  code = handleUnbalancedQuotes(
@@ -172,7 +177,7 @@ export const fez = (source, options = {}) => {
172
177
  throw new Error(
173
178
  'Top level expressions need to be wrapped in a (do) block'
174
179
  )
175
- const ast = [...(options.std ? treeShake(parsed, std) : []), ...parsed]
180
+ const ast = [...treeShake(parsed, std), ...parsed]
176
181
  if (options.compile) {
177
182
  const js = Object.values(comp(deepClone(ast))).join('')
178
183
  return options.eval ? eval(js) : js
@@ -199,51 +204,6 @@ export const fez = (source, options = {}) => {
199
204
  return err
200
205
  }
201
206
  }
202
-
203
- export const earMuffsToLodashes = (name) => name.replace(new RegExp(/\*/g), '_')
204
- export const dotNamesToEmpty = (name) => name.replace(new RegExp(/\./g), '')
205
- export const colonNamesTo$ = (name) => name.replace(new RegExp(/\:/g), '$')
206
- export const commaToLodash = (name) => name.replace(new RegExp(/\,/g), '_')
207
- export const arrowFromTo = (name) => name.replace(new RegExp(/->/g), '-to-')
208
- export const moduleNameToLodashes = (name) =>
209
- name.replace(new RegExp(/:/g), '_')
210
- export const questionMarkToLodash = (name) =>
211
- name.replace(new RegExp(/\?/g), 'Predicate')
212
- export const exclamationMarkMarkToLodash = (name) =>
213
- name.replace(new RegExp(/\!/g), 'Effect')
214
- export const toCamelCase = (name) => {
215
- let out = name[0]
216
- for (let i = 1; i < name.length; ++i) {
217
- const current = name[i],
218
- prev = name[i - 1]
219
- if (current === '-') continue
220
- else if (prev === '-') out += current.toUpperCase()
221
- else out += current
222
- }
223
- return out
224
- }
225
- export const keywordToHelper = (name) => {
226
- switch (name) {
227
- case KEYWORDS.ADDITION:
228
- return '__add'
229
- case KEYWORDS.MULTIPLICATION:
230
- return '__mult'
231
- case KEYWORDS.SUBTRACTION:
232
- return '__sub'
233
- case KEYWORDS.GREATHER_THAN:
234
- return '__gt'
235
- case KEYWORDS.EQUAL:
236
- return '__eq'
237
- case KEYWORDS.GREATHER_THAN_OR_EQUAL:
238
- return '__gteq'
239
- case KEYWORDS.LESS_THAN:
240
- return '__lt'
241
- case KEYWORDS.LESS_THAN_OR_EQUAL:
242
- return '__lteq'
243
- default:
244
- return name
245
- }
246
- }
247
207
  export const deepRename = (name, newName, tree) => {
248
208
  if (!isLeaf(tree))
249
209
  for (const leaf of tree) {
@@ -291,19 +251,3 @@ export const tree = (source, std) =>
291
251
  std
292
252
  ? shake(LISP.parse(removeNoCode(source)), std)
293
253
  : LISP.parse(removeNoCode(source))
294
- export const lispToJavaScriptVariableName = (name) =>
295
- toCamelCase(
296
- arrowFromTo(
297
- dotNamesToEmpty(
298
- colonNamesTo$(
299
- exclamationMarkMarkToLodash(
300
- questionMarkToLodash(
301
- commaToLodash(
302
- moduleNameToLodashes(earMuffsToLodashes(keywordToHelper(name)))
303
- )
304
- )
305
- )
306
- )
307
- )
308
- )
309
- )