This commit is contained in:
parent
8139b2a728
commit
caf52479e0
|
@ -0,0 +1,34 @@
|
|||
// includes
|
||||
const irc = require("irc");
|
||||
const lisp = require("./lisp").lisp;
|
||||
|
||||
// create the configuration
|
||||
const config = {
|
||||
channels: ["##qrxdkw", "##webpals"],
|
||||
server: "irc.libera.chat",
|
||||
port: 6667,
|
||||
botName: "blotbotboot"
|
||||
};
|
||||
|
||||
// create the bot name
|
||||
let bot = new irc.Client(config.server, config.botName, {
|
||||
channels: config.channels
|
||||
});
|
||||
|
||||
// listen for commands
|
||||
bot.addListener("message", function(from, to, text, message) {
|
||||
if (text.startsWith(",echo")) {
|
||||
const content = text.substring(6); // where 6 is the length of the trigger command, !echo + 1 space
|
||||
bot.say(to, content); // to ensures that the response goes to the channel the message was sent on
|
||||
} else if (text.startsWith(",pm")) {
|
||||
bot.say(from, "yes?"); // from makes this response a private message to the sender
|
||||
} else if (text.startsWith(",lisp")) {
|
||||
const code = text.substring(6);
|
||||
const ret = lisp.interpret(lisp.parse(code));
|
||||
bot.say(to, ret);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// run w/
|
||||
// $ node bot.js
|
|
@ -0,0 +1,413 @@
|
|||
// lifted from https://github.com/maryrosecook/littlelisp
|
||||
// extended by eli
|
||||
|
||||
(function(exports) {
|
||||
const library = {
|
||||
|
||||
print: (x) => {
|
||||
console.log(x);
|
||||
return x;
|
||||
},
|
||||
|
||||
display: (x) => {
|
||||
console.log(x);
|
||||
return x;
|
||||
},
|
||||
|
||||
concat: (...items) => {
|
||||
return items.reduce((acc, item) => {
|
||||
return `${acc}${item}`
|
||||
}, '')
|
||||
},
|
||||
|
||||
// math
|
||||
add: (...args) => {
|
||||
return args.reduce((sum, val) => sum + val)
|
||||
},
|
||||
|
||||
sub: (...args) => { // Subtracts values.
|
||||
return args.reduce((sum, val) => sum - val)
|
||||
},
|
||||
|
||||
mul: (...args) => { // Multiplies values.
|
||||
return args.reduce((sum, val) => sum * val)
|
||||
},
|
||||
|
||||
div: (...args) => { // Divides values.
|
||||
return args.reduce((sum, val) => sum / val)
|
||||
},
|
||||
|
||||
mod: (a, b) => { // Returns the modulo of a and b.
|
||||
return a % b
|
||||
},
|
||||
|
||||
clamp: (val, min, max) => { // Clamps a value between min and max.
|
||||
return Math.min(max, Math.max(min, val))
|
||||
},
|
||||
|
||||
step: (val, step) => {
|
||||
return Math.round(val / step) * step
|
||||
},
|
||||
|
||||
min: Math.min,
|
||||
max: Math.max,
|
||||
ceil: Math.ceil,
|
||||
floor: Math.floor, // round down to the nearest integer.
|
||||
sin: Math.sin,
|
||||
cos: Math.cos,
|
||||
log: Math.log, // calculates on the base of e.
|
||||
|
||||
pow: (a, b) => { // calculates a^b.
|
||||
return Math.pow(a, b)
|
||||
},
|
||||
|
||||
sqrt: Math.sqrt, // calculate the square root.
|
||||
|
||||
sq: (a) => { // calculate the square.
|
||||
return a * a
|
||||
},
|
||||
|
||||
PI: Math.PI,
|
||||
TWO_PI: Math.PI * 2,
|
||||
|
||||
random: (...args) => {
|
||||
if (args.length >= 2) {
|
||||
// (random start end)
|
||||
return args[0] + Math.random() * (args[1] - args[0])
|
||||
} else if (args.length === 1) {
|
||||
// (random max)
|
||||
return Math.random() * args[0]
|
||||
}
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
// logic
|
||||
gt: (a, b) => { // Returns true if a is greater than b, else false.
|
||||
return a > b
|
||||
},
|
||||
|
||||
lt: (a, b) => { // Returns true if a is less than b, else false.
|
||||
return a < b
|
||||
},
|
||||
|
||||
eq: (a, b) => { // Returns true if a is equal to b, else false.
|
||||
return a === b
|
||||
},
|
||||
|
||||
and: (a, b, ...rest) => { // Returns true if all conditions are true.
|
||||
const args = [a, b].concat(rest)
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (!args[i]) {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
return args[args.length - 1]
|
||||
},
|
||||
|
||||
or: (a, b, ...rest) => { // Returns true if at least one condition is true.
|
||||
const args = [a, b].concat(rest)
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i]) {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
return args[args.length - 1]
|
||||
},
|
||||
|
||||
// arrays
|
||||
map: async(fn, arr) => {
|
||||
let res = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const arg = arr[i]
|
||||
res.push(await fn(arr[i], i));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
filter: (fn, arr) => {
|
||||
const list = Array.from(arr)
|
||||
return Promise.all(list.map((element, index) => fn(element, index, list)))
|
||||
.then(result => {
|
||||
return list.filter((_, index) => {
|
||||
return result[index]
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
reduce: async(fn, arr, acc) => {
|
||||
const length = arr.length
|
||||
let result = acc === undefined ? subject[0] : acc
|
||||
for (let i = acc === undefined ? 1 : 0; i < length; i++) {
|
||||
result = await fn(result, arr[i], i, arr)
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
len: (item) => { // returns the length of a list.
|
||||
return item.length
|
||||
},
|
||||
|
||||
first: (arr) => { // returns the first item of a list.
|
||||
return arr[0]
|
||||
},
|
||||
|
||||
car: (arr) => { // returns the first item of a list.
|
||||
return arr[0]
|
||||
},
|
||||
|
||||
last: (arr) => { // returns the last
|
||||
return arr[arr.length - 1]
|
||||
},
|
||||
|
||||
rest: ([_, ...arr]) => {
|
||||
return arr
|
||||
},
|
||||
|
||||
cdr: ([_, ...arr]) => {
|
||||
return arr
|
||||
},
|
||||
|
||||
range: (start, end, step = 1) => {
|
||||
const arr = []
|
||||
if (step > 0) {
|
||||
for (let i = start; i <= end; i += step) {
|
||||
arr.push(i)
|
||||
}
|
||||
} else {
|
||||
for (let i = start; i >= end; i += step) {
|
||||
arr.push(i)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
},
|
||||
|
||||
// objects
|
||||
get: (item, key) => { // gets an object's parameter with name.
|
||||
return item[key]
|
||||
},
|
||||
|
||||
set: (item, ...args) => { // sets an object's parameter with name as value.
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
const key = args[i]
|
||||
const val = args[i + 1]
|
||||
item[key] = val
|
||||
}
|
||||
return item
|
||||
},
|
||||
|
||||
of: (h, ...keys) => { // gets object parameters with names.
|
||||
return keys.reduce((acc, key) => {
|
||||
return acc[key]
|
||||
}, h)
|
||||
},
|
||||
|
||||
keys: (item) => { // returns a list of the object's keys
|
||||
return Object.keys(item)
|
||||
},
|
||||
|
||||
values: (item) => { // returns a list of the object's values
|
||||
return Object.values(item)
|
||||
},
|
||||
|
||||
time: (rate = 1) => { // returns timestamp in milliseconds.
|
||||
return (Date.now() * rate)
|
||||
},
|
||||
|
||||
js: () => { // Javascript interop.
|
||||
return window // note, this only works in the browser
|
||||
},
|
||||
|
||||
test: (name, a, b) => {
|
||||
if (`${a}` !== `${b}`) {
|
||||
console.warn('failed ' + name, a, b)
|
||||
} else {
|
||||
console.log('passed ' + name, a)
|
||||
}
|
||||
return a === b
|
||||
},
|
||||
|
||||
benchmark: async(fn) => { // logs time taken to execute a function.
|
||||
const start = Date.now()
|
||||
const result = await fn()
|
||||
console.log(`time taken: ${Date.now() - start}ms`)
|
||||
return result
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const TYPES = {
|
||||
identifier: 0,
|
||||
number: 1,
|
||||
string: 2,
|
||||
bool: 3
|
||||
}
|
||||
|
||||
const Context = function(scope, parent) {
|
||||
this.scope = scope
|
||||
this.parent = parent
|
||||
this.get = function(identifier) {
|
||||
if (identifier in this.scope) {
|
||||
return this.scope[identifier]
|
||||
} else if (this.parent !== undefined) {
|
||||
return this.parent.get(identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const special = {
|
||||
let: function(input, context) {
|
||||
const letContext = input[1].reduce(function(acc, x) {
|
||||
acc.scope[x[0].value] = interpret(x[1], context)
|
||||
return acc
|
||||
}, new Context({}, context))
|
||||
return interpret(input[2], letContext)
|
||||
},
|
||||
def: function(input, context) {
|
||||
const identifier = input[1].value
|
||||
const value = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
|
||||
context.scope[identifier] = interpret(value, context)
|
||||
return value
|
||||
},
|
||||
defn: function(input, context) {
|
||||
const fnName = input[1].value
|
||||
const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
|
||||
const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3]
|
||||
context.scope[fnName] = async function() {
|
||||
const lambdaArguments = arguments
|
||||
const lambdaScope = fnParams.reduce(function(acc, x, i) {
|
||||
acc[x.value] = lambdaArguments[i]
|
||||
return acc
|
||||
}, {})
|
||||
return interpret(fnBody, new Context(lambdaScope, context))
|
||||
}
|
||||
},
|
||||
lambda: function(input, context) {
|
||||
return async function() {
|
||||
const lambdaArguments = arguments
|
||||
const lambdaScope = input[1].reduce(function(acc, x, i) {
|
||||
acc[x.value] = lambdaArguments[i]
|
||||
return acc
|
||||
}, {})
|
||||
return interpret(input[2], new Context(lambdaScope, context))
|
||||
}
|
||||
},
|
||||
if: async function(input, context) {
|
||||
if (await interpret(input[1], context)) {
|
||||
return interpret(input[2], context)
|
||||
}
|
||||
return input[3] ? interpret(input[3], context) : []
|
||||
},
|
||||
__fn: function(input, context) {
|
||||
return async function() {
|
||||
const lambdaArguments = arguments
|
||||
const keys = [...new Set(input.slice(2).flat(100).filter(i =>
|
||||
i.type === TYPES.identifier &&
|
||||
i.value[0] === '%'
|
||||
).map(x => x.value).sort())]
|
||||
const lambdaScope = keys.reduce(function(acc, x, i) {
|
||||
acc[x] = lambdaArguments[i]
|
||||
return acc
|
||||
}, {})
|
||||
return interpret(input.slice(1), new Context(lambdaScope, context))
|
||||
}
|
||||
},
|
||||
__obj: async function(input, context) {
|
||||
const obj = {}
|
||||
for (let i = 1; i < input.length; i += 2) {
|
||||
obj[await interpret(input[i], context)] = await interpret(input[i + 1], context)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
const interpretList = function(input, context) {
|
||||
if (input.length > 0 && input[0].value in special) {
|
||||
return special[input[0].value](input, context);
|
||||
} else {
|
||||
var list = input.map(function(x) {
|
||||
return interpret(x, context);
|
||||
});
|
||||
if (list[0] instanceof Function) {
|
||||
return list[0].apply(undefined, list.slice(1));
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const interpret = function(input, context) {
|
||||
if (context === undefined) {
|
||||
return interpret(input, new Context(library));
|
||||
} else if (input instanceof Array) {
|
||||
return interpretList(input, context);
|
||||
} else if (input.type === "identifier") {
|
||||
return context.get(input.value);
|
||||
} else if (input.type === "number" || input.type === "string") {
|
||||
return input.value;
|
||||
}
|
||||
};
|
||||
|
||||
let categorize = function(input) {
|
||||
if (!isNaN(parseFloat(input))) {
|
||||
return {
|
||||
type: 'number',
|
||||
value: parseFloat(input)
|
||||
};
|
||||
} else if (input[0] === '"' && input.slice(-1) === '"') {
|
||||
return {
|
||||
type: 'string',
|
||||
value: input.slice(1, -1)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'identifier',
|
||||
value: input
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let parenthesize = function(input, list) {
|
||||
if (list === undefined) {
|
||||
return parenthesize(input, []);
|
||||
} else {
|
||||
let token = input.shift();
|
||||
if (token === undefined) {
|
||||
return list.pop();
|
||||
} else if (token === "(") {
|
||||
list.push(parenthesize(input, []));
|
||||
return parenthesize(input, list);
|
||||
} else if (token === ")") {
|
||||
return list;
|
||||
} else {
|
||||
return parenthesize(input, list.concat(categorize(token)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let tokenize = function(input) {
|
||||
return input.split('"')
|
||||
.map(function(x, i) {
|
||||
if (i % 2 === 0) { // not in string
|
||||
return x.replace(/\(/g, ' ( ')
|
||||
.replace(/\)/g, ' ) ');
|
||||
} else { // in string
|
||||
return x.replace(/ /g, "!whitespace!");
|
||||
}
|
||||
})
|
||||
.join('"')
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.map(function(x) {
|
||||
return x.replace(/!whitespace!/g, " ");
|
||||
});
|
||||
};
|
||||
|
||||
let parse = function(input) {
|
||||
return parenthesize(tokenize(input));
|
||||
};
|
||||
|
||||
exports.lisp = {
|
||||
parse: parse,
|
||||
interpret: interpret
|
||||
};
|
||||
})(typeof exports === 'undefined' ? this : exports);
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "blotbotboot",
|
||||
"version": "1.0.0",
|
||||
"description": "small and mostly silent irc bot",
|
||||
"main": "bot.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"irc": "^0.5.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// lifted from https://github.com/maryrosecook/littlelisp
|
||||
var repl = require("repl");
|
||||
var lisp = require("./lisp").lisp;
|
||||
|
||||
repl.start({
|
||||
prompt: "* ",
|
||||
eval: function(cmd, context, filename, callback) {
|
||||
var ret = lisp.interpret(lisp.parse(cmd));
|
||||
callback(null, ret);
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue