{"id":51231100,"url":"https://github.com/harehare/parser_combinator.mq","last_synced_at":"2026-06-28T16:30:32.576Z","repository":{"id":366141234,"uuid":"1275211456","full_name":"harehare/parser_combinator.mq","owner":"harehare","description":"A small parser-combinator toolkit for mq, in the spirit of Rust's nom.","archived":false,"fork":false,"pushed_at":"2026-06-20T12:00:10.000Z","size":11,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T13:27:43.627Z","etag":null,"topics":["markdown","mq","parser-combinators"],"latest_commit_sha":null,"homepage":"https://mqlang.org","language":"jq","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/harehare.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-20T11:55:44.000Z","updated_at":"2026-06-20T12:00:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/harehare/parser_combinator.mq","commit_stats":null,"previous_names":["harehare/parser_combinator.mq"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/harehare/parser_combinator.mq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fparser_combinator.mq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fparser_combinator.mq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fparser_combinator.mq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fparser_combinator.mq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/harehare","download_url":"https://codeload.github.com/harehare/parser_combinator.mq/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/harehare%2Fparser_combinator.mq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34896652,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdown","mq","parser-combinators"],"created_at":"2026-06-28T16:30:30.815Z","updated_at":"2026-06-28T16:30:32.560Z","avatar_url":"https://github.com/harehare.png","language":"jq","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eparser_combinator.mq\u003c/h1\u003e\n\n\u003e [!WARNING]\n\u003e This project is under active development. APIs may change, and there are known performance issues, especially with large inputs or deeply nested grammars.\n\nA small parser-combinator toolkit for [mq](https://github.com/harehare/mq), in the spirit of Rust's [nom](https://github.com/rust-bakery/nom).\n\n## Concept\n\nA **parser** is just a function value with the signature `fn(input, pos): result;`. `result` is one of:\n\n```mq\n{ok: true,  pos: \u003cnew position\u003e, value: \u003cparsed value\u003e, error: None}\n{ok: false, pos: \u003cposition of failure\u003e, value: None,    error: \u003cmessage\u003e}\n```\n\nCombinators take one or more parsers and return a **new parser**, so they compose exactly like nom:\n\n```mq\nlet digits = pc_map(pc_take_while1(_pc_is_digit), to_number)\n```\n\nBecause parsers are ordinary mq function values, recursive grammars are just ordinary recursive `def`s — see [Recursive grammars](#recursive-grammars) below.\n\n## Installation\n\nCopy `parser_combinator.mq` to your mq module directory, or place it anywhere and reference it with `-L`.\n\n```sh\ncp parser_combinator.mq ~/.local/mq/config/\n```\n\n### HTTP Import (no local installation needed)\n\nIf `mq` was built with the `http-import` feature, you can import directly from GitHub without any local setup:\n\n```sh\nmq -I raw 'import \"github.com/harehare/parser_combinator.mq\" | parser_combinator::pc_run(parser_combinator::pc_tag(\"hello\"), .)' input.txt\n```\n\nPin to a specific release with `@vX.Y.Z`:\n\n```sh\nmq -I raw 'import \"github.com/harehare/parser_combinator.mq@v0.1.0\" | ...' input.txt\n```\n\n## Usage\n\n```sh\nmq -L /path/to/modules -I raw \\\n  'include \"parser_combinator\" | pc_run(pc_many1(pc_digit()), .)' input.txt\n```\n\n## API\n\n### Result helpers\n\n| Function | Description |\n|---|---|\n| `pc_ok(pos, value)` | Builds a success result. Use it when writing a custom parser by hand. |\n| `pc_err(pos, message)` | Builds a failure result. |\n| `pc_pos_to_line_col(input, pos)` | Returns `{line, col}` (1-based) for a position, for error messages. |\n\n### Primitives\n\nEach of these is a constructor — it returns a parser, it is not itself a parser.\n\n| Function | Description |\n|---|---|\n| `pc_char(c)` | Matches one exact character. |\n| `pc_any_char()` | Matches any single character. Fails at end of input. |\n| `pc_tag(s)` | Matches a literal string (nom's `tag`). |\n| `pc_satisfy(pred)` | Matches a single character for which `pred(ch)` is true. |\n| `pc_one_of(chars)` | Matches a single character that appears in `chars`. |\n| `pc_none_of(chars)` | Matches a single character that does **not** appear in `chars`. |\n| `pc_digit()` | Matches a single ASCII digit. |\n| `pc_alpha()` | Matches a single ASCII letter. |\n| `pc_alnum()` | Matches a single alphanumeric ASCII character. |\n| `pc_space()` | Matches a single whitespace character. |\n| `pc_regex(pattern)` | Matches the regex `pattern`, implicitly anchored at the current position. |\n| `pc_eof()` | Succeeds only at the end of input. Consumes nothing. |\n| `pc_rest()` | Always succeeds, consuming the rest of the input. |\n| `pc_take(n)` | Consumes exactly `n` characters. |\n| `pc_take_while(pred)` | Consumes characters while `pred(ch)` holds. Always succeeds (may match zero). |\n| `pc_take_while1(pred)` | Like `pc_take_while`, but fails on zero matches. |\n| `pc_take_until(s)` | Consumes up to (not including) the first occurrence of `s`. |\n| `pc_whitespace0()` | Zero or more whitespace characters. |\n| `pc_whitespace1()` | One or more whitespace characters. |\n\n### Transforming a parser's value\n\n| Function | Description |\n|---|---|\n| `pc_map(p, f)` | Transforms `p`'s value with `f` on success. |\n| `pc_value(p, v)` | Replaces `p`'s success value with the constant `v`. |\n| `pc_recognize(p)` | Replaces `p`'s value with the substring it consumed. |\n\n### Lookahead \u0026 optionality\n\n| Function | Description |\n|---|---|\n| `pc_opt(p)` | Tries `p`; always succeeds with `p`'s value or `None`. |\n| `pc_not(p)` | Negative lookahead: succeeds (consuming nothing) only if `p` fails. |\n| `pc_peek(p)` | Lookahead: runs `p` but never consumes input. |\n\n### Sequencing\n\n| Function | Description |\n|---|---|\n| `pc_pair(p1, p2)` | Runs `p1` then `p2`. Value is `[v1, v2]`. |\n| `pc_preceded(p1, p2)` | Runs `p1` then `p2`, keeping only `p2`'s value. |\n| `pc_terminated(p1, p2)` | Runs `p1` then `p2`, keeping only `p1`'s value. |\n| `pc_delimited(open, p, close)` | Runs `open`, `p`, `close`, keeping only `p`'s value. |\n| `pc_seq(parsers)` | Runs every parser in the array `parsers` in order. Value is an array. |\n| `pc_alt(parsers)` | Ordered choice: tries each parser in the array `parsers`, returns the first success. |\n\n### Repetition\n\n| Function | Description |\n|---|---|\n| `pc_many0(p)` | Zero or more `p`. Always succeeds. Value is an array. |\n| `pc_many1(p)` | One or more `p`. Fails on zero matches. |\n| `pc_count(p, n)` | Exactly `n` repetitions of `p`. |\n| `pc_sep_by0(p, sep)` | Zero or more `p` separated by `sep`. |\n| `pc_sep_by1(p, sep)` | One or more `p` separated by `sep`. |\n| `pc_many_till(p, end_p)` | Repeats `p` until `end_p` succeeds. Value is `{items: [...], end: \u003cend_p's value\u003e}`. |\n\n### Running a parser\n\n| Function | Description |\n|---|---|\n| `pc_parse(p, input)` | Runs `p` from position 0. Returns the raw `{ok, pos, value, error}` result; the input need not be fully consumed. |\n| `pc_run(p, input)` | Runs `p` and requires the **entire** input to be consumed. Returns the value directly on success, or raises a descriptive error (with line/column) on failure. |\n\n## Example: parsing comma-separated numbers\n\n```sh\nprintf '1,2,3' | mq -I raw 'include \"parser_combinator\" | pc_run(pc_sep_by1(pc_map(pc_take_while1(_pc_is_digit), to_number), pc_char(\",\")), .)'\n# =\u003e [1, 2, 3]\n```\n\n## Example: an arithmetic expression evaluator\n\nThis is the kind of small, ad-hoc parser this module is meant to make easy to write — a four-operator calculator with correct precedence and parentheses, built entirely from the combinators above:\n\n```mq\ninclude \"parser_combinator\"\n|\n\ndef ws(p): pc_terminated(p, pc_whitespace0());\n\ndef number():\n  ws(pc_map(pc_take_while1(_pc_is_digit), to_number))\nend\n\ndef apply_op(acc, pair):\n  let op = pair[0]\n  | let rhs = pair[1]\n  | if (op == \"+\"): acc + rhs\n    elif (op == \"-\"): acc - rhs\n    elif (op == \"*\"): acc * rhs\n    else: acc / rhs\nend\n\n# expr := term ((\"+\" | \"-\") term)*\ndef expr(input, pos):\n  let p = pc_pair(term, pc_many0(pc_pair(ws(pc_one_of(\"+-\")), term)))\n  | let r = p(input, pos)\n  | if (!r[\"ok\"]): r else: pc_ok(r[\"pos\"], fold(r[\"value\"][1], r[\"value\"][0], apply_op))\nend\n\n# term := factor ((\"*\" | \"/\") factor)*\ndef term(input, pos):\n  let p = pc_pair(factor, pc_many0(pc_pair(ws(pc_one_of(\"*/\")), factor)))\n  | let r = p(input, pos)\n  | if (!r[\"ok\"]): r else: pc_ok(r[\"pos\"], fold(r[\"value\"][1], r[\"value\"][0], apply_op))\nend\n\n# factor := number | \"(\" expr \")\"\ndef factor(input, pos):\n  let p = pc_alt([number(), pc_delimited(ws(pc_char(\"(\")), expr, ws(pc_char(\")\")))])\n  | p(input, pos)\nend\n\n| pc_run(ws(expr), \"2 * (3 + 4) - 5\")\n# =\u003e 9\n```\n\n`expr`, `term`, and `factor` call each other before all three are fully defined — that's fine, named `def`s in mq resolve by name at call time, so mutually recursive grammars are written exactly like you'd write them on paper.\n\n## Recursive grammars\n\nCombinators like `pc_many0` and `pc_alt` take a parser *value*. To build a parser that refers to itself (lists of lists, nested expressions, etc.), write a named `def` with the parser signature `(input, pos)` and call it by name — recursion and forward references between `def`s work without any extra setup:\n\n```mq\ndef item(input, pos):\n  let p = pc_alt([list_, number_()])\n  | p(input, pos)\nend\n\ndef list_(input, pos):\n  let p = pc_delimited(pc_char(\"[\"), pc_sep_by0(item, pc_char(\",\")), pc_char(\"]\"))\n  | p(input, pos)\nend\n\ndef number_():\n  pc_map(pc_take_while1(_pc_is_digit), to_number)\nend\n\n| pc_run(list_, \"[1,[2,3],4]\")\n# =\u003e [1, [2, 3], 4]\n```\n\n## A note on syntax\n\nmq does not allow chaining a call directly onto a call's result, e.g. `pc_many0(p)(input, pos)`. Bind the constructed parser to a name first:\n\n```mq\nlet many_digits = pc_many0(pc_digit())\n| many_digits(input, pos)\n```\n\n## License\n\nMIT\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharehare%2Fparser_combinator.mq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharehare%2Fparser_combinator.mq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharehare%2Fparser_combinator.mq/lists"}