UNPKG

fez-lisp

Version:

Lisp interpreted & compiled to JavaScript

252 lines (250 loc) 7.65 kB
import { APPLY, ATOM, TYPE, WORD, VALUE, STATIC_TYPES } from './keywords.js' export const leaf = (type, value) => [type, value] export const isLeaf = ([car]) => car === APPLY || car === ATOM || car === WORD export const LISP = { parse: (source) => { const tree = [] const stack = [tree] let head = tree let acc = '' for (let i = 0; i < source.length; ++i) { const cursor = source[i] if (cursor === '(') { const temp = [] head.push(temp) stack.push(head) head = temp } else if (cursor === ')' || cursor === ' ') { let token = acc acc = '' if (token) { if (!head.length) head.push(leaf(APPLY, token)) else if (token.match(/^-?[0-9]\d*(\.\d+)?$/)) head.push(leaf(ATOM, Number(token))) else head.push(leaf(WORD, token)) } if (cursor === ')') head = stack.pop() } else acc += cursor } return tree }, stringify: (array) => { if (typeof array === 'function') return '(lambda)' else if (typeof array === 'boolean') return +array else if (typeof array === 'object') if (Array.isArray(array)) return array.length ? `(array ${array.map(LISP.stringify).join(' ')})` : '(array)' else return `(array ${array .map(([key, value]) => `("${key}" ${LISP.stringify(value)})`) .join(' ')})` else return array }, json: (item) => { if (item === null) return 0 else if (typeof item === 'boolean') return item else if (typeof item === 'string') return `"${item}"` // return item.split('').map((x) => x.charCodeAt()) else if (typeof item === 'object') if (Array.isArray(item)) return item.length ? `[${item.map(LISP.json).join(' ')}]` : '[]' else return `({ ${Object.entries(item) .map(([key, value]) => `"${key}" ${LISP.json(value)}`) .join(' ')} })` else return item }, source: (ast) => { const dfs = (exp) => { let out = '' const [head, ...tail] = isLeaf(exp) ? [exp] : exp switch (head[TYPE]) { case WORD: out += head[VALUE] break case ATOM: out += head[VALUE] break case APPLY: out += `(${head[VALUE]} ${tail.map(dfs).join(' ')})` break } return out } return dfs(ast) } } export const AST = { parse: (source) => { const tree = [] const stack = [tree] let head = tree let acc = '' for (let i = 0; i < source.length; ++i) { const cursor = source[i] if (cursor === '"') { acc += '"' ++i while (source[i] !== '"') { acc += source[i] ++i } } if (cursor === '[') { const temp = [] head.push(temp) stack.push(head) head = temp } else if (cursor === ']' || cursor === ',') { let token = acc acc = '' if (token) { if (!head.length) head.push(Number(token)) else if (token[0] === '"' && token[token.length - 1] === '"') head.push(token.substring(1, token.length - 1)) else head.push(Number(token)) } if (cursor === ']') head = stack.pop() } else acc += cursor } return tree }, stringify: (ast) => typeof ast === 'object' ? `[${ast.map(AST.stringify).join(',')}]` : typeof ast === 'string' ? `"${ast}"` : ast, json: (ast) => { const dfs = (exp) => { let out = '' const [head, ...tail] = isLeaf(exp) ? [exp] : exp switch (head[TYPE]) { case WORD: out += `[${WORD},"${head[VALUE]}"]` break case ATOM: out += `[${ATOM},${head[VALUE]}]` break case APPLY: switch (head[VALUE]) { case STATIC_TYPES.ABSTRACTION: case STATIC_TYPES.COLLECTION: case STATIC_TYPES.UNKNOWN: case STATIC_TYPES.ATOM: case STATIC_TYPES.BOOLEAN: case STATIC_TYPES.ANY: case STATIC_TYPES.NUMBER: case STATIC_TYPES.NUMBERS: case STATIC_TYPES.ABSTRACTIONS: case STATIC_TYPES.BOOLEANS: case STATIC_TYPES.COLLECTIONS: case STATIC_TYPES.AS_NUMBER: out += dfs(tail[0]) break default: out += `[[${APPLY},"${head[VALUE]}"]${ tail.length ? ',' : '' }${tail.map(dfs).join(',')}]` break } break } return out } return dfs(ast) }, struct: (ast, mod = '') => { const dfs = (exp) => { let out = '' const [head, ...tail] = isLeaf(exp) ? [exp] : exp switch (head[TYPE]) { case WORD: out += `Expression::Word("${head[VALUE]}".to_string())` break case ATOM: out += `Expression::Atom(${ Number.isInteger(head[VALUE]) ? `${head[VALUE]}.0` : head[VALUE].toString() })` break case APPLY: switch (head[VALUE]) { case STATIC_TYPES.ABSTRACTION: case STATIC_TYPES.COLLECTION: case STATIC_TYPES.UNKNOWN: case STATIC_TYPES.ATOM: case STATIC_TYPES.BOOLEAN: case STATIC_TYPES.ANY: case STATIC_TYPES.NUMBER: case STATIC_TYPES.NUMBERS: case STATIC_TYPES.ABSTRACTIONS: case STATIC_TYPES.BOOLEANS: case STATIC_TYPES.COLLECTIONS: case STATIC_TYPES.AS_NUMBER: out += dfs(tail[0]) break default: out += `Expression::Apply(vec![Expression::Word("${ head[VALUE] }".to_string()),${tail.map(dfs).join(',')}])` break } break } return out } const str = dfs(ast) return mod ? `use crate::fez::Expression; pub fn ${mod}() -> Expression { ${str} }` : str }, ocaml: (ast) => { const dfs = (exp) => { let out = '' const [head, ...tail] = isLeaf(exp) ? [exp] : exp switch (head[TYPE]) { case WORD: out += `Word "${head[VALUE]}"` break case ATOM: { const n = Number.isInteger(head[VALUE]) ? `${head[VALUE]}.0` : head[VALUE].toString() out += `Atom ${head[VALUE] < 0 ? `(${n})` : n}` } break case APPLY: switch (head[VALUE]) { case STATIC_TYPES.ABSTRACTION: case STATIC_TYPES.COLLECTION: case STATIC_TYPES.UNKNOWN: case STATIC_TYPES.ATOM: case STATIC_TYPES.BOOLEAN: case STATIC_TYPES.ANY: case STATIC_TYPES.NUMBER: case STATIC_TYPES.NUMBERS: case STATIC_TYPES.ABSTRACTIONS: case STATIC_TYPES.BOOLEANS: case STATIC_TYPES.COLLECTIONS: case STATIC_TYPES.AS_NUMBER: out += dfs(tail[0]) break default: out += `Apply [Word "${head[VALUE]}"; ${tail .map(dfs) .join('; ')}]` break } break } return out } return dfs(ast) } }