Skip to content

Instantly share code, notes, and snippets.

@bodil
Last active August 29, 2015 14:12
Show Gist options
  • Save bodil/ee88f4fd30c18052e461 to your computer and use it in GitHub Desktop.
Save bodil/ee88f4fd30c18052e461 to your computer and use it in GitHub Desktop.
/* @flow -*- mode: flow -*- */
/*
* Example:
*
* var p = require("./parse");
*
* var parser = p.str([
* p.many1(p.letter),
* p.many(p.digit)
* ]);
*
* p.parse(parser, "foobar");
* => ["foobar", ""]
*
* p.parse(parser, "31337foobar");
* => null
*
* p.parse(parser, "foobar31337");
* => ["foobar31337", ""]
*
* p.parse(parser, "foo1337bar");
* => ["foo1337", "bar"]
*/
type ParseResult<A> = ?[A, string];
type Parser<A> = (input: string) => ParseResult<A>;
function parse<A>(parser: Parser<A>, input: string): ParseResult<A> {
return parser(input);
}
function seq<A, B>(p: Parser<A>, f: (a: A) => Parser<B>): Parser<B> {
return (input) => {
var out = parse(p, input);
return out != null ? parse(f(out[0]), out[1]) : null;
};
}
function either<A>(a: Parser<A>, b: Parser<A>): Parser<A> {
return (input) => {
return parse(a, input) || parse(b, input);
};
}
function ret<A>(value: A): Parser<A> {
return (input) => [value, input];
}
function fail<A>(input: string): ParseResult<A> {
return null;
}
function item(input: string): ParseResult<string> {
return input.length > 0 ? [input[0], input.slice(1)] : null;
}
function sat(p: (c: string) => boolean): Parser<string> {
return seq(item, (v) => p(v) ? ret(v) : fail);
}
var isDigit = (c) => /^\d$/.test(c);
var isSpace = (c) => /^\s$/.test(c);
var isAlphanum = (c) => /^\w$/.test(c);
var isLetter = (c) => /^[a-zA-Z]$/.test(c);
var isUpper = (c) => isLetter(c) && c == c.toUpperCase();
var isLower = (c) => isLetter(c) && c == c.toLowerCase();
var digit = sat(isDigit);
var space = sat(isSpace);
var alphanum = sat(isAlphanum);
var letter = sat(isLetter);
var upper = sat(isUpper);
var lower = sat(isLower);
function char(c: string): Parser<string> {
return sat((i) => i == c);
}
function string(s: string): Parser<string> {
if (s.length > 0) {
return seq(char(s[0]),
(_) => seq(string(s.slice(1)),
(_) => ret(s)));
} else {
return ret("");
}
}
function manyA<A>(p: Parser<A>): Parser<Array<A>> {
return either(many1A(p), ret([]));
}
function many1A<A>(p: Parser<A>): Parser<Array<A>> {
return seq(p,
(v) => seq(manyA(p),
(vs) => ret([v].concat(vs))));
}
function many(p: Parser<string>): Parser<string> {
return either(many1(p), ret(""));
}
function many1(p: Parser<string>): Parser<string> {
return seq(p,
(v) => seq(many(p),
(vs) => ret(v + vs)));
}
function str(ps: Array<Parser<string>>): Parser<string> {
return ps.length > 0 ? seq(ps[0], (v) => seq(str(ps.slice(1)), (vs) => ret(v + vs)))
: ret("");
}
module.exports = {
parse: parse,
seq: seq,
bind: seq,
either: either,
ret: ret,
fail: fail,
item: item,
sat: sat,
digit: digit,
letter: letter,
space: space,
upper: upper,
lower: lower,
alphanum: alphanum,
char: char,
string: string,
manyA: manyA,
many1A: many1A,
many: many,
many1: many1,
str: str
};
try {
module.exports.ParseResult = ParseResult;
module.exports.Parser = Parser;
} catch(e) {}
/* @flow -*- mode: flow -*- */
var p = require("./parse");
var g = require("./geom");
var t = require("./transform");
var degrees = Math.PI / 180;
var space = p.many(p.space);
var space1 = p.many1(p.space);
var num: p.Parser<number> = p.seq(
p.str([
p.either(p.char("-"), p.ret("")),
p.many(p.digit),
p.either(p.str([
p.char("."),
p.many1(p.digit)
]), p.ret(""))
]), (s) => {
var n = parseFloat(s);
return n == NaN ? p.fail : p.ret(n);
});
var point: p.Parser<g.Point> = p.seq(
num,
(x) => p.seq(p.char(","),
(_) => p.seq(num,
(y) => p.ret(g.point(x, y)))));
var translate: p.Parser<t.Transform> = p.seq(
p.char("t"),
(_) => p.seq(point,
(o) => p.ret(t.translatePoint(o))));
var rotate: p.Parser<t.Transform> = p.seq(
p.char("r"),
(_) => p.either(
p.seq(point,
(o) => p.seq(p.char(","),
(_) => p.seq(num,
(a) => p.ret(t.rotateAroundPoint(o, a * degrees))))),
// or
p.seq(num, (a) => p.ret(t.rotate(a * degrees)))));
var scale: p.Parser<t.Transform> = p.seq(
p.char("s"),
(_) => p.seq(point,
(o) => p.ret(t.scalePoint(o))));
var opacity: p.Parser<t.Transform> = p.seq(
p.char("o"),
(_) => p.seq(num,
(o) => (o >= 0 && o <= 1) ? p.ret(t.opacity(o)) : p.fail));
var transform = p.either(translate, p.either(rotate, p.either(scale, opacity)));
var transforms = p.seq(
p.manyA(p.seq(transform, (v) => p.seq(space, (_) => p.ret(v)))),
(ts) => p.ret(ts.reduce(t.compose, t.reset)));
function parse(s: string): t.Transform {
var parsed = p.parse(transforms, s);
if (parsed == null) {
throw new Error("Syntax error in transform descriptor: \"" + s + "\"");
} else {
if (parsed[1] === "") {
return parsed[0];
} else {
throw new Error("In transform descriptor \"" + s + "\": expected EOF, saw \"" +
parsed[1] + "\"");
}
}
}
module.exports = parse;
module.exports.parser = transforms;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment