Skip to content

Instantly share code, notes, and snippets.

@jaythomas
Last active September 19, 2018 01:49
Show Gist options
  • Save jaythomas/e890d92632ab91c6547e590c337f3550 to your computer and use it in GitHub Desktop.
Save jaythomas/e890d92632ab91c6547e590c337f3550 to your computer and use it in GitHub Desktop.
lisp-like language written in javascript
function tokenize(characters) {
return characters
.replace(/\s\s+/g, ' ')
.replace(/\(/g, ' ( ')
.replace(/\)/g, ' ) ')
.split(' ')
.filter(t => ' \t\n'.indexOf(t) === -1)
}
class Lipscript {
constructor() {
this.env = {
'=='(...args) {
const [a, b] = Array.from(args)[0]
return a === b
},
'!='(...args) {
const [a, b] = Array.from(args)[0]
return a !== b
},
'<'(...args) {
const [a, b] = Array.from(args)[0]
return a < b
},
'<='(...args) {
const [a, b] = Array.from(args)[0]
return a <= b
},
'>'(...args) {
const [a, b] = Array.from(args)[0]
return a > b
},
'>='(...args) {
const [a, b] = Array.from(args)[0]
return a >= b
},
'+'(...args) {
return Array.from(args)[0].reduce((acc, val) => {
return acc + val
})
},
'-'(...args) {
return Array.from(args)[0].reduce((acc, val) => {
return acc - val
})
},
'*'(...args) {
return Array.from(args)[0].reduce((acc, val) => {
return acc * val
})
},
'/'(...args) {
return Array.from(args)[0].reduce((acc, val) => {
return acc / val
})
},
true: true,
false: false
}
}
run(code) {
return this.eval(this.parse(code))
}
parse(program) {
return this.read(tokenize(program))
}
read(tokens) {
if (tokens.length === 0) {
return
}
// Grab the first token.
const token = tokens.shift()
if (token === '(') {
const list = []
while (tokens[0] !== ')') {
list.push(this.read(tokens))
}
// Keep going (since we may have nested lists).
tokens.shift()
return list
} else if (token === ')') {
throw new Error("Unexpected token ')'")
} else {
return this.atom(token)
}
}
atom(token) {
if ((/\.\d+/).test(token)) {
return parseFloat(token)
} else if ((/\d+/).test(token)) {
return parseInt(token)
} else if (this.env[token] && typeof this.env[token] !== 'function') {
return this.env[token]
}
return token.toString()
}
eval(expression) {
function handleArgs(args) {
const slicedArgs = [].slice.call(args)
for (let idx = 0; idx < slicedArgs.length; idx += 1) {
if (Array.isArray(slicedArgs[idx])) {
args[idx] = this.eval(args[idx])
}
}
return this.eval(args)
}
if (!expression) {
return
} else if (typeof expression === 'number' || typeof expression === 'boolean') {
return expression
} else if (Object.keys(this.env).indexOf(expression[0]) !== -1) {
const [fn] = expression
const args = expression.slice(1)
return this.env[fn](handleArgs.call(this, args))
} else if (expression[0] === 'def') {
const args = expression.slice(1)
this.env[args[0]] = args[1]
return this.eval(args[1])
} else if (expression[0] === 'if') {
const args = expression.slice(1)
const [test, conseq, alt] = args
return this.eval(test) ? conseq : alt
} else if (expression[0] === 'fn') {
const args = expression.slice(1)
const [params, body] = args
return new Function(params, this.eval(body))
}
const args = expression.slice(1)
const params = expression[0][1]
if (params) {
const body = [].slice.call(expression[0][2])
const bound = params.reduce((obj, key, idx) => ({ ...obj, [key]: args[idx] }), {})
const replace = (bound, body) => {
body = [].slice.call(body)
bound = Object.assign({}, bound)
for (let idx = 0; idx < body.length; idx += 1) {
if (Array.isArray(body[idx])) {
body[idx] = this.eval(replace(bound, body[idx]))
}
if (bound[body[idx]]) {
body[idx] = bound[body[idx]]
}
}
return this.eval(body)
}
return replace(bound, body)
}
return eval(expression)
}
}
const script = new Lipscript()
console.log(
script.run('(def pi 3.14159)'),
script.run('(def circle-area (fn (r) (* pi (* r r))))'),
script.run('(circle-area 10)')
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment