Skip to content

Instantly share code, notes, and snippets.

// A Stream is an abstraction over strings and arrays so that we don't have to
// keep chopping them up everywhere eating up CPU. An iterable is either a
// string or an array. The cursor is an index that marks the beginning of the
// stream and the length is the amount left in the Stream.
export class Stream {
constructor(iterable, cursor, length) {
this.iterable = iterable
this.cursor = cursor || 0
this.length = length === undefined
? iterable.length - this.cursor
const char = c => input => {
if (input[0] === c) {
return {success: true, rest: input.slice(1)}
}
return {success: false, rest: input}
}
char('a')('abc')
// => { success: true, rest: 'bc' }
const sequence = parsers => input => {
let next = input
for (var i = 0; i < parsers.length; i++) {
const parser = parsers[i]
const {success, rest} = parser(next)
if (!success) {
return {success, rest}
}
next = rest
}
const string = str => sequence(str.split('').map(char))
string('abc')('abcdefg')
// => { success: true, rest: 'defg' }
const either = parsers => input => {
for (var i = 0; i < parsers.length; i++) {
const parser = parsers[i]
const {success, rest} = parser(input)
if (success) {
return {success, rest}
}
}
return {success: false, rest: input}
}
class Stream {
constructor(iterable, cursor, length) {
this.iterable = iterable
this.cursor = cursor || 0
this.length = length === undefined
? iterable.length - this.cursor
: length
}
// Get the first value from the iterable.
head() {
class Result {
constructor(value, rest) {
this.value = value
this.rest = rest
}
}
class Success extends Result {
map(fn) {
return new Success(fn(this.value), this.rest)
class Parser {
constructor(parse) {
this.parse = parse
}
run(iterable) {
if (iterable instanceof Stream) {
return this.parse(iterable)
} else {
return this.parse(new Stream(iterable))
}
const char = c =>
new Parser(stream => {
if (stream.length === 0) {
return new Failure('unexpected end', stream))
}
const value = stream.head()
if (value === c) {
return new Success(value, stream.move(1))
}
return new Failure('char did not match', stream))
const where = predicate =>
new Parser(stream => {
if (stream.length === 0) {
return new Failure('unexpected end', stream)
}
const value = stream.head()
if (predicate(value)) {
return new Success(value, stream.move(1))
}
return new Failure('predicate did not match', stream)