├── .flowconfig
├── .gitignore
├── README.md
├── package.json
└── parse.js
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [options]
6 | module.system=node
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /index.js
2 | /node_modules/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Graham
2 |
3 | A Javascript port of the parser combinator library from Graham Hutton's [Programming in Haskell](http://www.cs.nott.ac.uk/~gmh/book.html).
4 |
5 | ## Usage
6 |
7 | ```sh
8 | $ npm install --save graham
9 | ```
10 |
11 | ```typescript
12 | var p = require("graham");
13 |
14 | var myLittleParser = p.either(
15 | p.seq(p.string("left "), (_) => p.seq(p.num,
16 | (n) => p.ret({ "left": n }))),
17 | p.seq(p.string("right "), (_) => p.seq(p.num,
18 | (n) => p.ret({ "right": n })))
19 | );
20 |
21 | p.parse(myLittleParser, "left 123");
22 | // == [{"left": 123}, ""]
23 |
24 | p.parse(myLittleParser, "right 456");
25 | // == [{"right": 456}, ""]
26 |
27 | p.parse(myLittleParser, "left lol");
28 | // == null
29 |
30 | p.parse(myLittleParser, "right 123lol");
31 | // == [{"right": 123}, "lol"]
32 | ```
33 |
34 | ## License
35 |
36 | Copyright 2014 Bodil Stokke
37 |
38 | This program is free software: you can redistribute it and/or modify
39 | it under the terms of the GNU Lesser General Public License as
40 | published by the Free Software Foundation, either version 3 of the
41 | License, or (at your option) any later version.
42 |
43 | This program is distributed in the hope that it will be useful, but
44 | WITHOUT ANY WARRANTY; without even the implied warranty of
45 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 | Lesser General Public License for more details.
47 |
48 | You should have received a copy of the GNU Lesser General Public
49 | License along with this program. If not, see
50 | .
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graham",
3 | "version": "1.2.1",
4 | "description": "Parser combinators for JS.",
5 | "main": "index.js",
6 | "scripts": {
7 | "prepublish": "babel -o index.js parse.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/bodil/graham"
12 | },
13 | "keywords": [
14 | "parser"
15 | ],
16 | "author": "Bodil Stokke",
17 | "license": "LGPL-3.0+",
18 | "bugs": {
19 | "url": "https://github.com/bodil/graham/issues"
20 | },
21 | "homepage": "https://github.com/bodil/graham",
22 | "devDependencies": {
23 | "babel": "^5.8.21"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/parse.js:
--------------------------------------------------------------------------------
1 | /* @flow -*- mode: flow -*- */
2 |
3 | type ParseResult = ?[A, string];
4 | type Parser = (input: string) => ParseResult;
5 |
6 | export function parse(parser: Parser, input: string): ParseResult {
7 | return parser(input);
8 | }
9 |
10 | export function run(genFunc) {
11 | return function(input) {
12 | const gen = genFunc();
13 | const runP = function runP(input, val) {
14 | const next = gen.next(val);
15 | if (next.done) {
16 | return [next.value, input];
17 | } else {
18 | const out = parse(next.value, input);
19 | if (out == null) {
20 | return null;
21 | } else {
22 | const [result, nextInput] = out;
23 | return runP(nextInput, result);
24 | }
25 | }
26 | }
27 | return runP(input);
28 | };
29 | }
30 |
31 | export function seq(p: Parser, f: (a: A) => Parser): Parser {
32 | return (input) => {
33 | const out = parse(p, input);
34 | return out != null ? parse(f(out[0]), out[1]) : null;
35 | };
36 | }
37 |
38 | export function either(a: Parser | Array>, b?: Parser): Parser {
39 | if (a instanceof Array) {
40 | const [head, ...tail] = a;
41 | return (tail.length) ? either(head, either(tail)) : head;
42 | } else {
43 | return (input) => {
44 | return parse(a, input) || parse(b, input);
45 | };
46 | }
47 | }
48 |
49 | export function ret(value: A): Parser {
50 | return (input) => [value, input];
51 | }
52 |
53 | export function fail(input: string): ParseResult {
54 | return null;
55 | }
56 |
57 | export function item(input: string): ParseResult {
58 | return input.length > 0 ? [input[0], input.slice(1)] : null;
59 | }
60 |
61 | export function sat(p: (c: string) => boolean): Parser {
62 | return seq(item, (v) => p(v) ? ret(v) : fail);
63 | }
64 |
65 | export const isDigit = (c) => /^\d$/.test(c);
66 | export const isSpace = (c) => /^\s$/.test(c);
67 | export const isAlphanum = (c) => /^\w$/.test(c);
68 | export const isLetter = (c) => /^[a-zA-Z]$/.test(c);
69 | export const isUpper = (c) => isLetter(c) && c == c.toUpperCase();
70 | export const isLower = (c) => isLetter(c) && c == c.toLowerCase();
71 |
72 | export const digit = sat(isDigit);
73 | export const space = sat(isSpace);
74 | export const alphanum = sat(isAlphanum);
75 | export const letter = sat(isLetter);
76 | export const upper = sat(isUpper);
77 | export const lower = sat(isLower);
78 |
79 | export function char(c: string): Parser {
80 | return sat((i) => i == c);
81 | }
82 |
83 | export function string(s: string): Parser {
84 | if (s.length > 0) {
85 | return seq(char(s[0]),
86 | (_) => seq(string(s.slice(1)),
87 | (_) => ret(s)));
88 | } else {
89 | return ret("");
90 | }
91 | }
92 |
93 | export function manyA(p: Parser): Parser> {
94 | return either(many1A(p), ret([]));
95 | }
96 |
97 | export function many1A(p: Parser): Parser> {
98 | return seq(p,
99 | (v) => seq(manyA(p),
100 | (vs) => ret([v].concat(vs))));
101 | }
102 |
103 | export function many(p: Parser): Parser {
104 | return either(many1(p), ret(""));
105 | }
106 |
107 | export function many1(p: Parser): Parser {
108 | return seq(p,
109 | (v) => seq(many(p),
110 | (vs) => ret(v + vs)));
111 | }
112 |
113 | export function str(ps: Array>): Parser {
114 | return ps.length > 0 ? seq(ps[0], (v) => seq(str(ps.slice(1)), (vs) => ret(v + vs)))
115 | : ret("");
116 | }
117 |
118 | export const spaces = many(space);
119 | export const spaces1 = many1(space);
120 |
121 | export const num: Parser = seq(
122 | str([
123 | either(char("-"), ret("")),
124 | many(digit),
125 | either(str([
126 | char("."),
127 | many1(digit)
128 | ]), ret(""))
129 | ]), (s) => {
130 | const n = parseFloat(s);
131 | return isNaN(n) ? fail : ret(n);
132 | });
133 |
134 | export const point = run(function*() {
135 | const x = yield num;
136 | yield char(",");
137 | const y = yield num;
138 | return {x, y};
139 | });
140 |
141 | export function makeParser(p: Parser): ((s: string) => A) {
142 | return (s) => {
143 | const parsed = parse(p, s);
144 | if (parsed == null) {
145 | throw new Error(`Syntax error in descriptor: "${s}"`);
146 | } else {
147 | if (parsed[1] === "") {
148 | return parsed[0];
149 | } else {
150 | throw new Error(`In descriptor "${s}": expected EOF, saw "${parsed[1]}"`);
151 | }
152 | }
153 | };
154 | }
155 |
--------------------------------------------------------------------------------