{"id":20425315,"url":"https://github.com/catseye/parc","last_synced_at":"2025-10-10T18:44:14.822Z","repository":{"id":149745380,"uuid":"579349836","full_name":"catseye/Parc","owner":"catseye","description":"MIRROR of https://codeberg.org/catseye/Parc : \"The simplest parser combinator library that could possibly work\". Fits on a page, no fancy types.","archived":false,"fork":false,"pushed_at":"2023-10-25T12:38:45.000Z","size":32,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-05T04:45:11.646Z","etag":null,"topics":["didactic","fits-on-a-page","minimal-code","minimal-viable-product","no-frills","parser-combinator","parser-combinators","simple-haskell","toy-library","toy-parser-combinators","toy-parser-generator"],"latest_commit_sha":null,"homepage":"https://catseye.tc/node/Parc","language":"Haskell","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/catseye.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}},"created_at":"2022-12-17T11:59:04.000Z","updated_at":"2023-10-24T18:44:12.000Z","dependencies_parsed_at":"2023-05-18T18:15:31.218Z","dependency_job_id":null,"html_url":"https://github.com/catseye/Parc","commit_stats":null,"previous_names":["catseye/parc"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/catseye/Parc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FParc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FParc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FParc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FParc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catseye","download_url":"https://codeload.github.com/catseye/Parc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2FParc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004916,"owners_count":26083803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"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":["didactic","fits-on-a-page","minimal-code","minimal-viable-product","no-frills","parser-combinator","parser-combinators","simple-haskell","toy-library","toy-parser-combinators","toy-parser-generator"],"created_at":"2024-11-15T07:12:52.176Z","updated_at":"2025-10-10T18:44:14.792Z","avatar_url":"https://github.com/catseye.png","language":"Haskell","readme":"Parc\n====\n\n_Version 0.1_ | _See also:_ [Tandem](https://codeberg.org/catseye/Tandem#tandem)\n∘ [Vinegar](https://codeberg.org/catseye/Vinegar#vinegar)\n\n- - - -\n\nParser combinator libraries are easy to write.  That's why GitHub is full of them.\n\nI wanted to write a particularly simple one, both to expose its structure,\nand to have something to experiment with.\n\n### `Parc`\n\nThe basic library is [`Parc.hs`](Parc.hs), about 35 lines long (including\ntype declarations and blank lines).\nSome code that uses it can be found in [`ParcDemo.hs`](ParcDemo.hs).\n\nBut `Parc.hs` is so simple that you can only build a recognizer with it.\nGenerally, you want to do more with the input string than say whether it\nis or is not in the language, yes?  So, there are more extended\nversions here too.\n\n(Before getting to the extended versions, I want to highlight one thing,\nwhich is that the choice operator in Parc is _ordered choice_, as used in\ne.g. PEG parsers.  This is arguably less mathematically nice than\nthe _non-deterministic choice_ used in e.g. context-free grammars,\nbut it is unarguably simpler to implement.)\n\n### `ParcSt`\n\nAn extension that adds state to the parser, making it a _stateful parser_,\nis [`ParcSt.hs`](ParcSt.hs).\nSome code that uses it can be found in [`ParcStDemo.hs`](ParcStDemo.hs).\n\nThis lets you write an actual parser, with an actual output value.\n\nBut even so, the facility it provides for state management is rather crude.\nThe type of the state variable is polymorphic, so the programmer can\nchoose any type they like for it.  But every parser combinator assumes\nthe state it will take as input is the same type as the state it will\nproduce on output.  In a strictly typed language, that can be a bit\nlimiting.  Ideally, we want to write parsers that parse different types of\nthings, then tie them together into bigger, composite parsers.\n\n(This isn't a problem in a dynamically typed language, but in such\nlanguages you have a different problem -- namely, the possibility of\na type mismatch somewhere in the composite parser which will only become\napparent at runtime.)\n\n### `ParcSt2St`\n\nSo we have a very small modification called [`ParcSt2St.hs`](ParcSt2St.hs).\nIn this, the `Parser` type is polymorphic on two types, one for input\nand one for output.\n\nSome code that uses this can be found in [`ParcSt2StDemo.hs`](ParcSt2StDemo.hs).\nIn fact, it is virtually a drop-in replacement for `ParcSt.hs`, as the type\nsignature is simply more general.\n\nIt does illuminate the types of the combinators somewhat though; `seq` has a\ntype that corresponds to transitivity, while `many` requires that the output\ntype is the same as the input type, which makes sense if you think of it as\na loop that may occur any number of times -- only an output that can be fed\nback into the input would make sense there.\n\n### `ParcMerge`\n\nTo allow finer manipulation of the parsing state, we extend `ParcSt2St.hs`\nwith an extra combinator.  We can't build this combinator out of existing\ncombinators; it needs to access the parse state directly.  Also, its\ndefinition turns out to be somewhat more complicated.  So, this is where\nwe start leaving \"fits on a page\" territory.\n\nThe [`ParcMerge.hs`](ParcMerge.hs) module extends `ParcSt2St.hs` with a\ncombinator called `merge`.  `merge` takes a parser P and a function F which\ntakes two state values and returns a third state value as input; it\nproduces a new parser as its output.  This new parser applies P, then\nproduces a new state by combining the state before P, and the state produced\nby P, using F.\n\n[`ParcMergeDemo.hs`](ParcMergeDemo.hs) demonstrates how this `merge`\ncombinator can be used to apply arithmetic operators like `+` and `*`\nwhen parsing an arithmetic expression.\n\n### `ParcAssert`\n\nWith what we have so far, if we merely accumulate state as we parse,\nwe can parse only context-free languages.\n\nBut if we enable the parser to succeed or fail based on some predicate\napplied to the parsing state, we can parse context-sensitive languages.\n\n[`ParcAssertDemo.hs`](ParcAssertDemo.hs) uses `ParcSt2St` and adds a\ncombinator called `assert` that takes such a predicate on the parsing\nstate, and produces a parser which succeeds only when that predicate\nis true on the current parsing state, failing otherwise.\n\nThe demo module gives an example of a parser for a classic\ncontext-free language: strings of the form `a`^_n_ `b`^_n_ `c`^_n_.\n\n### `ParcConsume`\n\n_This is where it starts getting markedly experimental._\n_Feel free to stop reading now._\n\nWith `ParcAssert`, we can parse context-sensitive languages.  But\nif we don't restrict ourselves to passing sufficiently simple predicates\nto `assert`, we can parse languages quite beyond the context-sensitive.\nFor example, we could use a predicate that checks if the\nstring passed to it is a valid sentence in Presburger arithmetic, or\neven a valid computation trace of a Turing machine.\n\nFormulating a set of parser combinators which is provably capable\nof parsing _only_ the context-sensitive languages seems like a hard\nproblem.  One could probably create combinators that can describe\nany context-sensitive grammar (CSG), or an equivalent formalism such as\na noncontracting grammar.  But these formalisms are actually extremely\ninefficient ways to parse most context-sensitive languages.\n\nWe can take some small steps though.\n\nFirst, we can establish a proviso that we only ever expect the functions\nthat are passed to our higher-order combinators to do a small amount of\ncomputation.  For concreteness, say polynomial time.  (We could in fact\nassemble a library of \"state manipulation combinators\" and ensure those\nare the only ones that can be used, to enforce this.)\n\nBut that by itself isn't enough.  If we allow combinators that recurse or\nloop without consuming input, we can still perform arbitrarily large\namounts of computation.\n\nSo we get rid of `ok` and `many` and `update`, and instead of `assert`,\nuse require the use of `pred`, which always consumes a character before it\nchecks if it should succeed (possibly modifying the state) or fail.\n\nAnd the result is [`ParcConsume.hs`](ParcConsume.hs), which is a lot\nlike `ParcSt2St`, just with some things ripped out of it.\n\n[`ParcConsumeDemo.hs`](ParcConsumeDemo.hs) shows that it can recognize\na context-sensitive language, and that the means by which it does so\nrelies on it consuming its input; and the lack of other means of manipulating\nthe state should be persuasive that it is limited in its ability to\nmake these calculations.  Exactly how limited, though?  I'm not\nquite prepared to make a claim on that yet.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fparc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatseye%2Fparc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fparc/lists"}