fez-lisp 1.0.22 → 1.0.24

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.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) =>