I had a task to reduce my Lisp Interpreter from 400 lines to 120 lines. I was able to bring it down to 255 lines without including newline after every function. I'm not able to figure out where I can reduce the lines of code. I wanted a different perspective to my code probably a repeating pattern which i'm not able to find. What all improvements can I push to my code?
var input = require('fs').readFileSync('newexample.txt', 'utf-8')
const ENV = {}
let mat, regexp = /^[0-9]+/, output
const spaceParser = (input) => (mat = input.match(/^[\s\t\n]+/)) ? [mat[0], input.slice(mat[0].length)] : null
const identifierParser = (input) => (mat = input.match(/^[a-z]+[0-9]*[a-z]*/i)) ? [mat[0], input.slice(mat[0].length)] : null
const numberParser = (input) => (mat = regexp.exec(input)) ? [parseInt(mat[0]), input.slice(mat[0].length)] : null
const stringParser = (data) => {
if (data[0] === '"') {
data = data.substring(1, data.length)
let i = data.search('"')
return ([data.substring(0, i), data.slice(i + 1)])
}
return null
}
const add = input => input.reduce((accum, value) => (ENV[value]) ? accum + ENV[value] : accum + value, 0)
const sub = input => input.reduce((accum, value) => (ENV[value]) ? accum - ENV[value] : accum - value)
const mul = input => input.reduce((accum, value) => (ENV[value]) ? accum * ENV[value] : accum * value, 1)
const div = input => input.reduce((accum, value) => (ENV[value]) ? accum / ENV[value] : accum / value)
const greaterThan = (a, b) => a > b
const lessThan = (a, b) => a < b
const greaterThanEqualTo = (a, b) => a >= b
const lessThanEqualTo = (a, b) => a <= b
const EqualTo = (a, b) => a === b
const notNumber = (a) => !a
const maxNumber = (a, b) => a > b ? a : b
const minNumber = (a, b) => a < b ? a : b
const listCall = (a) => { return { type: 'list', args: a } }
const listCar = (a) => a[0].shift()
const listCdr = (a) => {
a[0].shift()
return a[0]
}
const listCons = (a) => {
a[1].unshift(a[0])
return a[1]
}
const isListLisp = (a) => !!(Array.isArray(a))
const defineLisp = (a, b) => { ENV[a] = b }
const printLisp = (a) => (ENV[a]) ? console.log(ENV[a]) : a
const ifLisp = (a, b, c) => a ? b : c
const plusParser = (data) => data.startsWith('+') ? [add, data.slice(1)] : null
const minusParser = (data) => data.startsWith('-') ? [sub, data.slice(1)] : null
const mulParser = (data) => data.startsWith('*') ? [mul, data.slice(1)] : null
const divParser = (data) => data.startsWith('/') ? [div, data.slice(1)] : null
const greaterThanParser = (data) => data.startsWith('>') ? [greaterThan, data.slice(1)] : null
const lessThanParser = (data) => data.startsWith('<') ? [lessThan, data.slice(1)] : null
const greaterThanEqualToParser = (data) => data.startsWith('>=') ? [greaterThanEqualTo, data.slice(2)] : null
const lessThanEqualToParser = (data) => data.startsWith('<=') ? [lessThanEqualTo, data.slice(2)] : null
const equalToParser = (data) => data.startsWith('==') ? [EqualTo, data.slice(2)] : null
const maxParser = (data) => data.startsWith('max') ? [maxNumber, data.slice(3)] : null
const minParser = (data) => data.startsWith('min') ? [minNumber, data.slice(3)] : null
const notParser = (data) => data.startsWith('not') ? [notNumber, data.slice(3)] : null
const listParser = (data) => data.startsWith('list') ? [listCall, data.slice(4)] : null
const carList = (data) => data.startsWith('car') ? [listCar, data.slice(3)] : null
const cdrList = (data) => data.startsWith('cdr') ? [listCdr, data.slice(3)] : null
const consList = (data) => data.startsWith('cons') ? [listCons, data.slice(4)] : null
const isListIden = (data) => data.startsWith('isList') ? [isListLisp, data.slice(6)] : null
const defineSlicerParser = (data) => data.startsWith('define') ? [defineLisp, data.slice(6)] : null
const ifSlicerParser = (data) => data.startsWith('if') ? [ifLisp, data.slice(2)] : null
const printSlicerParser = (data) => data.startsWith('print') ? [printLisp, data.slice(5)] : null
const lambdaSlicerParser = (data) => data.startsWith('lambda') ? ['lambda', data.slice(6)] : null
const openBracket = (input) => (input.startsWith('(')) ? ['(', input.slice(1)] : null
const closeBracket = (input) => (input.startsWith(')')) ? [')', input.slice(1)] : null
const parserFactory = (...parsers) => (input) => {
for (let parser of parsers) {
let output = parser(input)
if (output !== null) return output
}
return null
}
const allParser = (...parsers) => (input) => {
let result = []
for (let parser of parsers) {
let output = parser(input)
if (output === null) return null
result.push(output[0])
input = output[1]
}
return [result, input]
}
const operatorFinder = (input) => parserFactory(plusParser, minusParser, mulParser,
divParser, greaterThanEqualToParser, lessThanEqualToParser, equalToParser,
greaterThanParser, lessThanParser, maxParser, minParser, notParser, listParser,
carList, cdrList, consList, isListIden)(input)
const operatorParser = (input) => {
let count = 1
if (!input.startsWith('(')) return null
input = input.slice(1)
if (input.startsWith('>') || input.startsWith('>=') ||
input.startsWith('<') || input.startsWith('<=') ||
input.startsWith('==') || input.startsWith('max') ||
input.startsWith('min')) {
count++
}
let output = operatorFinder(input)
if (output === null) return null
// For Operator Operand1 Operand2
let result = [], vid = '', args = 'args'
result.push(output[0])
while (output[0] !== ')') {
output = spaceParser(output[1])
output = expressionParser(output[1])
if (ENV[output[0]]) {
(ENV[output[0]].args) ? result.push(ENV[output[0]].args) : result.push(ENV[output[0]])
} else {
result.push(output[0])
}
output = (vid = closeBracket(output[1])) ? vid : output
}
if (output[0] === ')') {
if (output[1] === '') {
return [evaluate(result, count)]
} else {
return [evaluate(result, count), output[1]]
}
}
}
const evaluate = (input, count) => {
let operation = input.shift()
if (count > 1) return operation(...input)
else return operation(input)
}
const expressionParser = (input) => parserFactory(numberParser, stringParser,
lambdaParser, identifierParser, operatorParser)(input)
const lambdaArgumentsParser = (input) => {
let result = [], output
input = openBracket(input)
input = input[1]
while (!input.startsWith(')')) {
input = identifierParser(input)
result.push(input[0])
input = (output = spaceParser(input[1])) ? output[1] : input[1]
}
input = closeBracket(input)
return [result, input[1]]
}
const lambdaBodyParser = (input) => {
let count = 0, k = 0, output = ''
do {
if (input[k] === '(') count++
if (input[k] === ')') count--
output += input[k]
k++
} while (count !== 0)
input = input.slice(k)
return [output, input]
}
const defineLambda = (input) => {
let obj = {}, count = 3
let output = allParser(openBracket, lambdaSlicerParser, spaceParser, lambdaArgumentsParser,
spaceParser, lambdaBodyParser, closeBracket)(input)
if (output === null) return null
let [[, Type, , Args, , Body], rest] = output
obj.type = Type, obj.args = Args, obj.body = Body
return [obj, rest, count]
}
const checker = (output) => {
if ((output[0] === 'list') || (output[0] === 'max') || (output[0] === 'min') ||
(output[0] === 'car') || (output[0] === 'cdr') || (output[0] === 'cons') ||
(output[0] === 'isList')) {
return null
}
}
const lambdaParser = (input) => {
if (!input.startsWith('(')) return null
input = input.slice(1)
let output = identifierParser(input)
let type = 'type'
if ((output === null) || (checker(output) === null) || (ENV[output[0]].type === undefined)) return null
let key = output[0], arr = []
while (!output[1].startsWith(')')) {
output = spaceParser(output[1])
if (numberParser(output[1])) {
output = numberParser(output[1])
arr.push(output[0])
}
if (identifierParser(output[1])) {
output = identifierParser(output[1])
if (ENV[output[0]] !== undefined) {
output[0] = ENV[output[0]]
arr.push(output[0])
}
}
if (output[1].startsWith('(')) {
output = lambdaParser(output[1])
arr.push(output[0][0])
}
}
let value = output[0], args = 'args', body = 'body', env = {}
for (let i = 0; i < arr.length; i++) {
env[ENV[key].args[i]] = arr[i]
}
env[body] = ENV[key].body
for (let i = 0; i < arr.length; i++) {
env[body] = env[body].replace(ENV[key].args[i], env[ENV[key].args[i]])
}
env[body] = env[body].replace(ENV[key].args[0], value)
env[body] = env[body].replace(ENV[key].args[0], value)
output = (vid = closeBracket(output[1])) ? vid : output
if (output[0] === ')') {
return [operatorParser(env[body]), output[1]]
}
}
const defineParser = (input) => {
let arr = [], count = 1
let output = allParser(openBracket, defineSlicerParser, spaceParser, identifierParser,
spaceParser)(input)
if (output === null) return null
let [[ , defineFunc, , iden], rest] = output
input = rest
output = defineLambda(input)
if (output === null) output = expressionParser(input)
let [val1, val2] = output
arr.push(defineFunc, iden, val1)
count += 3
evaluate(arr, count)
input = closeBracket(val2)
return input[1]
}
const printParser = (input) => {
let arr = [], count = 1
let output = allParser(openBracket, printSlicerParser, spaceParser, expressionParser,
closeBracket)(input)
if (output === null) return null
let [[, printFunc, , exp], rest] = output
arr.push(printFunc)
ENV[exp] ? arr.push(ENV[exp]) : arr.push(exp)
console.log('Evaluate to ' + evaluate(arr, count))
if (rest === null) {
return null
}
return rest
}
const ifParser = (input) => {
let arr = [], count = 0
let output = allParser(openBracket, ifSlicerParser, spaceParser, expressionParser, spaceParser, expressionParser, spaceParser, expressionParser, closeBracket)(input)
if (output === null) return null
let [[, ifFun, , test, , conseq, , alt], rest] = output
arr.push(ifFun, test, conseq, alt)
console.log('Evaluate to ' + evaluate(arr, count + 3))
return rest
}
const statementParser = (input) => parserFactory(defineParser, printParser, ifParser)(input)
const programParser = (input) => {
while (input !== '' && input !== null) {
let spaceParsed = ''
input = (spaceParsed = spaceParser(input)) ? spaceParsed[1] : input
input = statementParser(input)
}
return ENV
}
console.log((output = programParser(input)) ? output : 'Error')
if
statements could be turned into nested ternary-operator trees to get them on one line. Also, having something likek++
on its own line avoids the original intention of the++
operator, which was basically to avoid needing a new line to iteratek
. \$\endgroup\$