fez-lisp 1.0.22 → 1.0.24

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.22",
5
+ "version": "1.0.24",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "keywords": [
package/src/compiler.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  VALUE,
8
8
  WORD,
9
9
  } from './enums.js'
10
+ import { leaf, isLeaf } from './parser.js'
10
11
  import { deepRename, lispToJavaScriptVariableName } from './utils.js'
11
12
  const Helpers = {
12
13
  log: `var logEffect=(msg)=>{console.log(msg);return msg}`,
@@ -28,14 +29,6 @@ return result
28
29
  isAtom: `atomPredicate=(value)=>typeof value==='number'||typeof value==='string'`,
29
30
  error: `_error=(error)=>{
30
31
  throw new Error(error)
31
- }`,
32
- arraySet: `arraySet=(array,index,value)=>{
33
- array=[...array]
34
- if (index < 0) {
35
- const target = array.length + index
36
- while (array.length !== target) array.pop()
37
- } else array[index] = value;
38
- return array
39
32
  }`,
40
33
  arraySetEffect: `arraySetEffect=(array,index,value)=>{
41
34
  if (index < 0) {
@@ -45,7 +38,7 @@ return result
45
38
  return array
46
39
  }`,
47
40
  serialise:
48
- "serialise=(ast)=>{\n if(ast==undefined) return '()'\n else if(typeofast==='object')\n if(Array.isArray(ast)) return `(array ${ast.map(stringify).join(' ')})`\n else\n return `(array ${Object.entries(ast).map(([key, value]) => `(\"${key}\" ${stringify(value)})`).join(' ')})`\n else if(typeofast==='string') return `\"${ast}\"`\n else if(typeofast==='function') return '()'\n else return ast\n}",
41
+ "serialise=(ast)=>{\n if(ast==undefined) return '()'\n else if(typeof ast==='object')\n if(Array.isArray(ast)) return `(array ${ast.map(stringify).join(' ')})`\n else\n return `(array ${ast.map(([key, value]) => `(\"${key}\" ${stringify(value)})`).join(' ')})`\n else if(typeof ast==='string') return `\"${ast}\"`\n else if(typeof ast==='function') return '()'\n else return ast\n}",
49
42
  cast: `_cast=(type,value)=>{
50
43
  switch (type) {
51
44
  case '${KEYWORDS.NUMBER_TYPE}':
@@ -95,7 +88,7 @@ const parseArgs = (Arguments, Variables, separator = ',') =>
95
88
  parse(Arguments, Variables).join(separator)
96
89
  const compile = (tree, Variables) => {
97
90
  if (!tree) return ''
98
- const [first, ...Arguments] = Array.isArray(tree) ? tree : [tree]
91
+ const [first, ...Arguments] = !isLeaf(tree) ? tree : [tree]
99
92
  if (first == undefined) return '[];'
100
93
  const token = first[VALUE]
101
94
  if (first[TYPE] === APPLY) {
@@ -186,8 +179,8 @@ const compile = (tree, Variables) => {
186
179
  return `((${parseArgs(
187
180
  functionArgs.map((node, index) =>
188
181
  node[VALUE] === PLACEHOLDER
189
- ? { [TYPE]: node[TYPE], [VALUE]: `_${index}` }
190
- : { [TYPE]: node[TYPE], [VALUE]: node[VALUE] }
182
+ ? leaf(node[TYPE], `_${index}`)
183
+ : leaf(node[TYPE], node[VALUE])
191
184
  ),
192
185
  Variables
193
186
  )})=>{${vars}return ${evaluatedBody.toString().trimStart()}});`
@@ -318,15 +311,10 @@ const compile = (tree, Variables) => {
318
311
  }
319
312
  case KEYWORDS.IMMUTABLE_FUNCTION: {
320
313
  const [first, ...rest] = Arguments
321
- return compile(
322
- [{ [TYPE]: APPLY, [VALUE]: first[VALUE] }, ...rest],
323
- Variables
324
- )
314
+ return compile([leaf(APPLY, first[VALUE]), ...rest], Variables)
325
315
  }
326
316
  case KEYWORDS.SERIALISE:
327
317
  return `serialise(${compile(Arguments[0], Variables)});`
328
- case KEYWORDS.SET_IMMUTABLE_ARRAY:
329
- return `arraySet(${parseArgs(Arguments, Variables)});`
330
318
  case KEYWORDS.SET_ARRAY:
331
319
  return `arraySetEffect(${parseArgs(Arguments, Variables)});`
332
320
  case KEYWORDS.NOT_COMPILED_BLOCK:
package/src/enums.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // AST enums
2
+ export const TYPE = 0
3
+ export const VALUE = 1
2
4
  export const WORD = 'w'
3
5
  export const APPLY = 'f'
4
- export const VALUE = 'v'
5
- export const TYPE = 't'
6
6
  export const ATOM = 'a'
7
7
  // tokeniser enums
8
8
  export const PLACEHOLDER = '.'
@@ -77,5 +77,4 @@ export const KEYWORDS = {
77
77
  SERIALISE: 'serialise',
78
78
 
79
79
  SET_ARRAY: 'array:set!',
80
- SET_IMMUTABLE_ARRAY: 'array:set',
81
80
  }
@@ -1,9 +1,10 @@
1
1
  import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './enums.js'
2
+ import { isLeaf } from './parser.js'
2
3
  import { keywords } from './tokeniser.js'
3
4
  import { stringifyArgs } from './utils.js'
4
5
 
5
6
  export const evaluate = (exp, env) => {
6
- const [first, ...rest] = Array.isArray(exp) ? exp : [exp]
7
+ const [first, ...rest] = !isLeaf(exp) ? exp : [exp]
7
8
  if (first == undefined) return []
8
9
  switch (first[TYPE]) {
9
10
  case WORD: {
package/src/parser.js CHANGED
@@ -1,6 +1,7 @@
1
- import { APPLY, ATOM, TYPE, VALUE, WORD } from './enums.js'
1
+ import { APPLY, ATOM, WORD } from './enums.js'
2
2
  import { escape } from './utils.js'
3
-
3
+ export const leaf = (type, value) => [type, value]
4
+ export const isLeaf = (tree) => typeof tree[0] === 'string'
4
5
  export const parse = (source) => {
5
6
  const tree = []
6
7
  let head = tree,
@@ -25,18 +26,12 @@ export const parse = (source) => {
25
26
  let token = acc
26
27
  acc = ''
27
28
  if (token) {
28
- if (!head.length) head.push({ [TYPE]: APPLY, [VALUE]: token })
29
+ if (!head.length) head.push(leaf(APPLY, token))
29
30
  else if (token.match(/^"([^"]*)"/))
30
- head.push({
31
- [TYPE]: ATOM,
32
- [VALUE]: token.substring(1, token.length - 1),
33
- })
31
+ head.push(leaf(ATOM, token.substring(1, token.length - 1)))
34
32
  else if (token.match(/^-?[0-9]\d*(\.\d+)?$/))
35
- head.push({
36
- [TYPE]: ATOM,
37
- [VALUE]: Number(token),
38
- })
39
- else head.push({ [TYPE]: WORD, [VALUE]: token })
33
+ head.push(leaf(ATOM, Number(token)))
34
+ else head.push(leaf(WORD, token))
40
35
  }
41
36
  if (cursor === ')') head = stack.pop()
42
37
  } else acc += cursor
@@ -46,10 +41,10 @@ export const parse = (source) => {
46
41
  export const stringify = (ast) => {
47
42
  if (ast == undefined) return '()'
48
43
  else if (typeof ast === 'object')
49
- if (Array.isArray(ast))
44
+ if (!isLeaf(ast))
50
45
  return ast.length ? `(array ${ast.map(stringify).join(' ')})` : '()'
51
46
  else
52
- return `(array ${Object.entries(ast)
47
+ return `(array ${ast
53
48
  .map(([key, value]) => `("${key}" ${stringify(value)})`)
54
49
  .join(' ')})`
55
50
  else if (typeof ast === 'string') return `"${ast}"`
package/src/tokeniser.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TYPE, VALUE, WORD, KEYWORDS } from './enums.js'
1
+ import { TYPE, VALUE, WORD, KEYWORDS, APPLY } from './enums.js'
2
2
  import { evaluate } from './interpreter.js'
3
3
  import { stringify } from './parser.js'
4
4
  import {
@@ -14,9 +14,9 @@ const keywords = {
14
14
  if (args.length < 2)
15
15
  throw new RangeError(
16
16
  `Invalid number of arguments for (${
17
- keywords.CONCATENATION
17
+ KEYWORDS.CONCATENATION
18
18
  }), expected > 1 but got ${args.length}. (${
19
- keywords.CONCATENATION
19
+ KEYWORDS.CONCATENATION
20
20
  } ${stringifyArgs(args)})`
21
21
  )
22
22
  const operands = args.map((x) => evaluate(x, env))
@@ -870,11 +870,13 @@ const keywords = {
870
870
  )
871
871
  let inp = args[0]
872
872
  for (let i = 1; i < args.length; ++i) {
873
- if (!Array.isArray(args[i]))
873
+ if (!args[i].length || args[i][0][TYPE] !== APPLY)
874
874
  throw new TypeError(
875
- `Argument at position (${i}) of (${KEYWORDS.PIPE}) is not a (${
876
- KEYWORDS.ANONYMOUS_FUNCTION
877
- }). (${KEYWORDS.PIPE} ${stringifyArgs(args)})`
875
+ `Argument at position (${i}) of (${
876
+ KEYWORDS.PIPE
877
+ }) is not an invoked (${KEYWORDS.ANONYMOUS_FUNCTION}). (${
878
+ KEYWORDS.PIPE
879
+ } ${stringifyArgs(args)})`
878
880
  )
879
881
  const [first, ...rest] = args[i]
880
882
  const arr = [first, inp, ...rest]
@@ -1088,86 +1090,6 @@ const keywords = {
1088
1090
  }
1089
1091
  return array
1090
1092
  },
1091
-
1092
- [KEYWORDS.SET_IMMUTABLE_ARRAY]: (args, env) => {
1093
- if (args.length !== 2 && args.length !== 3)
1094
- throw new RangeError(
1095
- `Invalid number of arguments for (${
1096
- KEYWORDS.SET_IMMUTABLE_ARRAY
1097
- }) (or 2 3) required (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
1098
- args
1099
- )})`
1100
- )
1101
- let array = evaluate(args[0], env)
1102
- if (!Array.isArray(array))
1103
- throw new TypeError(
1104
- `First argument of (${KEYWORDS.SET_IMMUTABLE_ARRAY}) must be an (${
1105
- KEYWORDS.ARRAY_TYPE
1106
- }) but got (${array}) (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
1107
- args
1108
- )})`
1109
- )
1110
- array = [...array]
1111
- const index = evaluate(args[1], env)
1112
- if (!Number.isInteger(index))
1113
- throw new TypeError(
1114
- `Second argument of (${KEYWORDS.SET_IMMUTABLE_ARRAY}) must be an (${
1115
- KEYWORDS.NUMBER_TYPE
1116
- } integer) (${index}) (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(
1117
- args
1118
- )})`
1119
- )
1120
- if (index > array.length)
1121
- throw new RangeError(
1122
- `Second argument of (${
1123
- KEYWORDS.SET_IMMUTABLE_ARRAY
1124
- }) is outside of the (${
1125
- KEYWORDS.ARRAY_TYPE
1126
- }) bounds (index ${index} bounds ${array.length}) (${
1127
- KEYWORDS.SET_IMMUTABLE_ARRAY
1128
- } ${stringifyArgs(args)})`
1129
- )
1130
- if (index < 0) {
1131
- if (args.length !== 2)
1132
- throw new RangeError(
1133
- `Invalid number of arguments for (${
1134
- KEYWORDS.SET_IMMUTABLE_ARRAY
1135
- }) (if (< index 0) then 2 required) (${
1136
- KEYWORDS.SET_IMMUTABLE_ARRAY
1137
- } ${stringifyArgs(args)})`
1138
- )
1139
- if (index * -1 > array.length)
1140
- throw new RangeError(
1141
- `Second argument of (${
1142
- KEYWORDS.SET_IMMUTABLE_ARRAY
1143
- }) is outside of the (${
1144
- KEYWORDS.ARRAY_TYPE
1145
- }) bounds (index ${index} bounds ${array.length}) (${
1146
- KEYWORDS.SET_IMMUTABLE_ARRAY
1147
- } ${stringifyArgs(args)})`
1148
- )
1149
- const target = array.length + index
1150
- while (array.length !== target) array.pop()
1151
- } else {
1152
- if (args.length !== 3)
1153
- throw new RangeError(
1154
- `Invalid number of arguments for (${
1155
- KEYWORDS.SET_IMMUTABLE_ARRAY
1156
- }) (if (>= index 0) then 3 required) (${
1157
- KEYWORDS.SET_IMMUTABLE_ARRAY
1158
- } ${stringifyArgs(args)})`
1159
- )
1160
- const value = evaluate(args[2], env)
1161
- if (value == undefined)
1162
- throw new RangeError(
1163
- `Trying to set a null value in (${KEYWORDS.ARRAY_TYPE}) at (${
1164
- KEYWORDS.SET_IMMUTABLE_ARRAY
1165
- }). (${KEYWORDS.SET_IMMUTABLE_ARRAY} ${stringifyArgs(args)})`
1166
- )
1167
- array[index] = value
1168
- }
1169
- return array
1170
- },
1171
1093
  }
1172
1094
  keywords[KEYWORDS.NOT_COMPILED_BLOCK] = keywords[KEYWORDS.BLOCK]
1173
1095
  export { keywords }
package/src/utils.js CHANGED
@@ -1,8 +1,16 @@
1
1
  import std from '../lib/baked/std.js'
2
2
  import { comp } from './compiler.js'
3
- import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD } from './enums.js'
3
+ import {
4
+ APPLY,
5
+ ATOM,
6
+ KEYWORDS,
7
+ PLACEHOLDER,
8
+ TYPE,
9
+ VALUE,
10
+ WORD,
11
+ } from './enums.js'
4
12
  import { evaluate, run } from './interpreter.js'
5
- import { parse } from './parser.js'
13
+ import { isLeaf, parse } from './parser.js'
6
14
  export const logError = (error) => console.log('\x1b[31m', error, '\x1b[0m')
7
15
  export const logSuccess = (output) => console.log(output, '\x1b[0m')
8
16
  export const removeNoCode = (source) =>
@@ -39,13 +47,13 @@ export const escape = (Char) => {
39
47
  }
40
48
  }
41
49
  export const stringifyType = (type) =>
42
- Array.isArray(type)
50
+ !isLeaf(type)
43
51
  ? `(array ${type.map((t) => stringifyType(t)).join(' ')})`
44
52
  : typeof type
45
53
  export const stringifyArgs = (args) =>
46
54
  args
47
55
  .map((x) =>
48
- Array.isArray(x)
56
+ !isLeaf(x)
49
57
  ? `(${stringifyArgs(x)})`
50
58
  : x[TYPE] === APPLY || x[TYPE] === WORD
51
59
  ? x[VALUE]
@@ -132,7 +140,7 @@ export const treeShake = (ast, libs) => {
132
140
  const deps = libs.reduce((a, x) => a.add(x.at(1)[VALUE]), new Set())
133
141
  const visited = new Set()
134
142
  const dfs = (tree) => {
135
- if (Array.isArray(tree)) tree.forEach(dfs)
143
+ if (!isLeaf(tree)) tree.forEach(dfs)
136
144
  else if (
137
145
  (tree[TYPE] === APPLY || tree[TYPE] === WORD) &&
138
146
  deps.has(tree[VALUE]) &&
@@ -153,20 +161,18 @@ export const runFromCompiled = (source) => {
153
161
  const tree = parse(
154
162
  handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
155
163
  )
156
- if (Array.isArray(tree)) {
157
- const compiled = comp(tree)
158
- const JavaScript = `${compiled.top}${compiled.program}`
159
- return eval(JavaScript)
160
- }
164
+ const compiled = comp(tree)
165
+ const JavaScript = `${compiled.top}${compiled.program}`
166
+ return eval(JavaScript)
161
167
  }
162
168
  export const runFromInterpreted = (source, env = {}) => {
163
169
  const tree = parse(
164
170
  handleUnbalancedQuotes(handleUnbalancedParens(removeNoCode(source)))
165
171
  )
166
- if (Array.isArray(tree)) return run(tree, env)
172
+ run(tree, env)
167
173
  }
168
174
  export const dfs = (tree, callback) => {
169
- if (Array.isArray(tree)) for (const branch of tree) dfs(branch)
175
+ if (!isLeaf(tree)) for (const leaf of tree) dfs(leaf)
170
176
  else callback(tree)
171
177
  }
172
178
  export const deepClone = (ast) => JSON.parse(JSON.stringify(ast))
@@ -180,7 +186,12 @@ export const fez = (source, options = {}) => {
180
186
  )
181
187
  else code = removeNoCode(source)
182
188
  if (!options.mutation) code = removeMutation(code)
189
+ if (!code.length && options.throw) throw new Error('Nothing to parse!')
183
190
  const parsed = parse(code)
191
+ if (parsed.length === 0 && options.throw)
192
+ throw new Error(
193
+ 'Top level expressions need to be wrapped in a (do) block'
194
+ )
184
195
  const standard = options.std
185
196
  ? options.shake
186
197
  ? treeShake(parsed, std)
@@ -196,9 +207,8 @@ export const fez = (source, options = {}) => {
196
207
  const err = error.message
197
208
  .replace("'[object Array]'", '(array)')
198
209
  .replace('object', '(array)')
199
- if (options.errors) {
200
- logError(err)
201
- }
210
+ if (options.errors) logError(err)
211
+ if (options.throw) throw err
202
212
  return err
203
213
  }
204
214
  }
@@ -227,12 +237,12 @@ export const toCamelCase = (name) => {
227
237
  return out
228
238
  }
229
239
  export const deepRename = (name, newName, tree) => {
230
- if (Array.isArray(tree))
231
- for (const branch of tree) {
240
+ if (!isLeaf(tree))
241
+ for (const leaf of tree) {
232
242
  // Figure out a non mutable solution so
233
243
  // I can get rid of deep copy
234
- if (branch[VALUE] === name) branch[VALUE] = `()=>${newName}`
235
- deepRename(name, newName, branch)
244
+ if (leaf[VALUE] === name) leaf[VALUE] = `()=>${newName}`
245
+ deepRename(name, newName, leaf)
236
246
  }
237
247
  }
238
248
  export const lispToJavaScriptVariableName = (name) =>