Skip to content

Instantly share code, notes, and snippets.

@jahan-addison
Last active December 21, 2017 15:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jahan-addison/f72417f6c1789caa434219351b665193 to your computer and use it in GitHub Desktop.
Save jahan-addison/f72417f6c1789caa434219351b665193 to your computer and use it in GitHub Desktop.
DSL in Javascript (Typescript)
ƛ({
server: 'irc.freenode.org',
nick: 'dslbot',
channel: '##dslbottesting'
},
Connect(6667)(
line => on(/PING (\S+)/)(_ => {
server`PONG ${_}`
}),
line => on(/hello/)(_ => {
reply`Howdy`
}),
line => on(/dslbot\?/)(_ => {
reply`I was written in a DSL-ish!`
})
),
error => '💥 Something went wrong.'
);
export type Maybe<T> = Some<T> | None;
export type None = () => undefined;
export type Some<T> = () => T;
export const None: None = () => void(0);
export function Some<T>(_: T): Some<T> {
return () => _;
}
export function match<T>(_: Maybe<T>, some: (_: any) => any, none: Function) {
if (Guard<T>(_)) {
return some(_());
} else {
return none();
}
}
export function Guard<T>(_: Maybe<T>): _ is Maybe<T> {
return (<Maybe<T>>_)() !== None();
}
export interface Authentication {
server: string,
nick: string,
channel: string
}
import { None, Some, match, Maybe, Authentication } from './types';
import {
createConnection,
Socket
} from 'net';
// State Reification
export interface Machine {
auth: Maybe<Authentication>,
port: Maybe<number>,
line: Maybe<string>,
connection: Maybe<Socket>,
error: Maybe<Function>,
connected: boolean,
}
export async function connect(state: Machine): Promise<Machine> {
const auth = state.auth;
const port = state.port;
return match<Authentication>(auth,
credentials => {
return state.connection = Some<Socket>(createConnection({
host: credentials.server, port: match<number>(port,
_ => _,
_ => 6667)
}, () => {
state.connected = true;
authenticate(state);
})), state;
},
_ => {
throw new ReferenceError('No credentials provided');
});
}
export function authenticate(state: Machine): void {
match<Authentication>(state.auth,
credentials => match<Socket>(state.connection,
connection => {
connection.write(`USER ${credentials.nick} * * :bot\r\n`);
connection.write(`NICK ${credentials.nick}\r\n`);
setTimeout(() => {
connection.write(`JOIN ${credentials.channel}\r\n`);
}, 1000);
},
_ => {
throw new ReferenceError('Connection not established');
}),
_ => {
throw new ReferenceError('Not authenticated');
});
}
import { None, Some, match, Authentication } from './types';
import * as Irc from './irc';
import { Socket } from 'net';
let State: Irc.Machine = {
connection: None,
auth: Some({
server: 'irc.freenode.org',
nick: 'dslbot',
channel: '##dslbottesting'
}),
port: None,
line: None,
error: None,
connected: false,
}
function applyMany(context: Irc.Machine, applied: Function[]): void {
applied.forEach((callback: Function) => {
match<string>(context.line,
line => {
callback.apply(line);
},
_ => {
console.warn('context was insufficient');
}
);
});
}
export function ƛ(details: Authentication, _, Error: Function) {
State.auth = Some(details);
State.error = Some(Error);
}
export function Connect(port: number): Function {
State.port = Some(port);
return (...execution) => {
try {
Irc.connect(State).then(state => {
match<Socket>(State.connection,
client => {
client.on('data', data => {
State.line = Some(data.toString().split(/\r\n/)[0]);
applyMany(State, execution);
});
},
_ => {
throw new ReferenceError('Failed to connect');
}
);
});
} catch(e) {
match<Function>(State.error,
call => call(),
_ => {
console.error('Uh oh ⚡️', e);
});
}
};
}
export function server(prefix, response): boolean {
return match<Socket>(State.connection,
connection => connection.write(`${prefix[0]} :${response}\r\n`)
_ => false);
}
export function reply(response): boolean {
return match<Socket>(State.connection,
connection => {
const where = match<Authentication>(State.auth,
credentials => credentials.channel,
() => '##dslbottesting');
return connection.write(`PRIVMSG ${where} :${response}\r\n`);
},
_ => false);
}
export function on(what: RegExp): Function {
return (callback) => {
match<string>(State.line,
line => {
if (what.test(line)) {
const capture = what.exec(line);
callback(capture ? capture[1] : line.split(/:/)[2]);
}
},
_ => {
throw new ReferenceError('Connection not established');
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment