Last active
August 29, 2015 14:12
-
-
Save bodil/ee88f4fd30c18052e461 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @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) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @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