{"id":48684661,"url":"https://github.com/tmanderson/pc","last_synced_at":"2026-04-11T03:56:14.601Z","repository":{"id":57166847,"uuid":"450953538","full_name":"tmanderson/pc","owner":"tmanderson","description":"P(arser)C(ombinator) - a minimal zero-dependency parser combinator framework enabling intuitive and modular parser development","archived":false,"fork":false,"pushed_at":"2023-12-13T03:28:18.000Z","size":105,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-11T03:56:05.332Z","etag":null,"topics":["framework","functional","minimal","parser","parser-combinator","parser-combinators","parser-framework","parser-generator","parser-library","parsercombinator","parsing","simple"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tmanderson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2022-01-22T22:46:39.000Z","updated_at":"2022-04-18T13:17:36.000Z","dependencies_parsed_at":"2024-09-18T23:54:21.151Z","dependency_job_id":"ac785af3-ddec-45ea-a8ae-34e8ac113101","html_url":"https://github.com/tmanderson/pc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tmanderson/pc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmanderson%2Fpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmanderson%2Fpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmanderson%2Fpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmanderson%2Fpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tmanderson","download_url":"https://codeload.github.com/tmanderson/pc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmanderson%2Fpc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31668050,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T17:19:37.612Z","status":"online","status_checked_at":"2026-04-11T02:00:05.776Z","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":["framework","functional","minimal","parser","parser-combinator","parser-combinators","parser-framework","parser-generator","parser-library","parsercombinator","parsing","simple"],"created_at":"2026-04-11T03:56:13.987Z","updated_at":"2026-04-11T03:56:14.582Z","avatar_url":"https://github.com/tmanderson.png","language":"JavaScript","readme":"# P(arser)C(ombinator)\n\nPC is a minimal zero-dependency [parser combinator](https://en.wikipedia.org/wiki/Parser_combinator)\nframework enabling intuitive and modular parser development.\n\nA parser as we refer to it here is a function with the signature\n```\n(input: string) =\u003e [offset, matches]\n```\n\nWhere `offset` indicates how far into `input` we were able to convert into `matches`.\n\nPC provides four fundamental parsers:\n\n- `string` for matching exact strings (e.g. `\"hi\" === [\"hi\"]`)\n- `regexp` for matching character ranges (e.g. `/hi?/ === [\"h\", \"hi\"]`)\n- `sequence` for matching ordered patterns of parsers (i.e. all patterns must match, one after the other)\n- `any` for matching any number of patterns in any order (i.e. at least one pattern must match)\n\nBoth the `string` and `regexp` parser can be created with the `match` parser,\nwhich is just a convenience function which maps your argument (a `string` or\n`RegExp`) to the `string` or `regexp` parser.\n\nAll parsers in PC have the following signature:\n\n```ts\n(input: string) =\u003e [offset: number, matches: string[] | string | null]\n```\n\nWhere `input` is the remaining input to be parsed, `offset` is the length of input\nconsumed or matched by the parser and `matches` is an array of strings or single\nstring (signifying a successful match) or `null` (signifying no match). See the\n[Types](#Types) section for more detail.\n\n### Install\n\n```\nnpm i @tmanderson/pc\n```\n\n### Example\n\n#### JSON Parser\n\n```js\nconst { match: m, sequence: s, any: a } = require('@tmanderson/pc');\n// Helper for patterns matching once and only once\nconst m11 = p =\u003e m(p, 1, 1);\n// Special Characters\nconst CBO = m11('{')\nconst CBC = m11('}')\nconst HBO = m11('[')\nconst HBC = m11(']')\nconst COL = m11(':')\nconst COM = m11(',')\nconst QOT = m11('\"')\nconst TRU = m11('true')\nconst FLS = m11('false')\nconst INT = m11(/[0-9]/)\nconst ALP = m11(/[a-zA-Z0-9]/)\nconst DOT = m11('.')\nconst CHA = m11(/[^\"]/)\n// Optional Whitespace\nconst WSP = m(/[\\n\\s\\t ]/, 0)\n// \"Primitives\"\nconst BOO = a([ TRU, FLS ], 1, 1);\nconst STR = s([ QOT, m(i =\u003e CHA(i), 0), QOT ], 1, 1);\nconst NUM = s([ INT, s([ DOT, INT ], 0) ]);\n// Arrays (ENT = array-entry)\nconst ENT = s([ WSP, i =\u003e TYP(i), WSP ])\nconst ARR = s([ HBO, s([ ENT, s([ COM, ENT ], 0) ], 0), HBC ]);\n// Objects (KAV = key-and-value)\nconst KAV = s([ WSP, a([ STR, ALP ]), WSP, COL, WSP, i =\u003e TYP(i), WSP ]);\nconst OBJ = s([ CBO, s([ KAV, s([ COM, KAV ], 0) ], 0), CBC ]);\n// Value types\nconst TYP = a([ STR, NUM, BOO, OBJ, ARR ]);\n// Root\nconst JSON = a([ ARR, OBJ ], 0, 1);\n\nJSON('{}')\nJSON('[]')\nJSON('{ test: true }')\nJSON('{ \"test\": [1, \"two\", true, {}] }')\n```\n\n### Formatting output\n\nAll PC parsers take a single argument (an `input` string) and return a [`MatcherResult`](#Types).\nThis makes interstitial operations (within the parsing context) a matter of defining\na function with this input/ouput signature. Within that function you can manipulate\ninput, output, the parser offset and/or the outputs of other parsers called within\nthe function itself.\n\nA common use-case of this might be in the concatenation of consecutive `string`\nmatches. For example, the parser `match('a')` would, given the input `'aaab'`,\nreturn `['a', 'a', 'a']` which can become daunting when reading through your parser\noutput. It would be better if the output were `['aaa']`. We can resolve this issue\nby creating a `concat` utility for our simple parser:\n\n```js\nconst SimpleParser = match('a');\nSimpleParser('aaab') // =\u003e [ 3, [ 'a', 'a', 'a' ] ]\n\nconst concat = (input) =\u003e {\n  // SimpleParser returns a PrimitiveMatch [number, string]\n  const [inputOffset, matches] = SimpleParser(input);\n  // if `matches` is null, this implies no matches (so inputOffset is 0)\n  if (matches === null) return [0, null];\n  // Otherwise return the same offset (we're not reducing/consuming extra input)\n  // and concatenate all the matches from AlphaN\n  return [inputOffset, matches.join('')]\n}\n\nconcat('aaab') // =\u003e [ 3, [ 'aaa' ] ]\n```\n\nIf you're one for concision, this function can be greatly minimized with an\n[IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE):\n\n```js\nconst concat = (input) =\u003e\n  (([inputOffset, matches]) =\u003e\n    [inputOffset, matches ? matches.join('') : null])(SimpleParser(input))\n```\n\n## API\n\n### `match(pattern: string | RegExp, min?: number, max?: number): MatcherResult`\n\nThe `match` parser takes a `pattern`. If `pattern` is a `RegExp` remember that\nit will only match against _a single character of input_ at a time (because the\nlength of a match is assumed _intentionally_ indeterminate).\n\n```js\nmatch('wow')('wow') // =\u003e [3, 'wow']\nmatch('wow')('wowwow') // =\u003e [6, ['wow', 'wow']]\nmatch('wow')('wowow') // =\u003e [3, 'wow']\n\nmatch(/[wo]/)('wo') // =\u003e [2, ['w' ,'o']]\nmatch(/[wo]/)('wowww') // =\u003e [5, ['w', 'o', 'w', 'w', 'w']]\n```\n\n### `sequence(patterns: Array\u003cMatcher\u003e, min?: number, max?: number): MatcherResult`\n\nThe `sequence` parser takes an ordered array of `Matcher`s, returning an array\nof tokens, each entry pertaining to the match specified within the `patterns`.\n\n```js\nsequence([\n  match('w'),\n  match('o'),\n  match('w')\n])('wow') // =\u003e [ 3, [ [ ['w'], ['o'], ['w'] ] ] ]\n\nsequence([\n  match(/[0-9]/, 3),\n  match('-'),\n  match(/[0-9]/, 3),\n  match('-'),\n  match(/[0-9]/, 4),\n])('123-456-7890') /* =\u003e\n[ 12, [\n    [\n      [ '1', '2', '3' ],\n      [ '-' ],\n      [ '4', '5', '6' ],\n      [ '-' ],\n      [ '7', '8', '9', '0' ]\n    ]\n  ]\n] */\n```\n\n### `any(patterns: Array\u003cMatcher\u003e, min?: number, max?: number): MatcherResult`\n\nThe `any` parser takes an unordered array of `Matcher`s, returning an array\nof tokens, each entry pertaining to _any_ match within the `patterns`.\n\n```js\nany([\n  match(/[0-9]/),\n  match(/[a-z ]/),\n  match('Jodabalocky'),\n])('Jodabalocky is 77') // =\u003e\n// [ 17, [ [ 'jodabalocky' ], [ ' ', 'i', 's', ' ' ], [ '7', '7' ] ] ]\n```\n\n## Types\n\nAll parsers utilized by PC require an output of `MatcherResult`. The following\nbreaks down the definition a bit more:\n\n```ts\ntype NoMatch = null;\ntype Match = string;\ntype Matches = string[];\n\ntype MatcherResult = [offset: number, matches: MatchGroup | Match | NoMatch];\n```\n\nThe `offset` value represents the total number of input characters consumed by\nthe parser while the second argument represents the matches made by it. If `matches`\nreturns `null` this indicates that the input was not successfully parsed.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmanderson%2Fpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftmanderson%2Fpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmanderson%2Fpc/lists"}