{"_id":"@beppobert/ts-combinator","name":"@beppobert/ts-combinator","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@beppobert/ts-combinator","version":"0.0.1","private":false,"description":"This library is a proof of concept and is not intended for production use. It aims to align an HKT implementation with runtime code. It implements a simple parser combinator library. You can use it to implement a typesafe JSON parser or GraphQL query pars","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"test":"vitest","coverage":"vitest run --coverage"},"author":{"name":"Philipp Dehler"},"license":"ISC","devDependencies":{"@vitest/coverage-v8":"^1.2.2","typescript":"^5.3.3","vitest":"^1.2.2"},"repository":{"type":"git","url":"git+https://github.com/Beppobert/ts-combinator.git"},"keywords":["typescript","hkt","parser","combinator","parser-combinator"],"bugs":{"url":"https://github.com/Beppobert/ts-combinator/issues"},"homepage":"https://github.com/Beppobert/ts-combinator#readme","_id":"@beppobert/ts-combinator@0.0.1","gitHead":"1208e242ae8ca67ef3ef1cfe9c9d7f978b2409fa","_nodeVersion":"18.14.0","_npmVersion":"9.8.0","dist":{"integrity":"sha512-zP213wWa9OexVBTx2Fq2dFVgc0bLs0jpoVTSlyNTEhZpemsQCTqOC8lnVHiQ7jsq/Ax4LICYBM+IQNl3D3sKAQ==","shasum":"66966763a8cdb4b57671e49dd01c88f8f1e7ede9","tarball":"https://registry.npmjs.org/@beppobert/ts-combinator/-/ts-combinator-0.0.1.tgz","fileCount":196,"unpackedSize":217015,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDv53xIxz1PCpOfURFmQ20xsefQR+6k1hfBPkymnMoimAIgSEqLUZKzNabPWOz+677FJNflySfzWOuV7pZhQD+Pj78="}]},"_npmUser":{"name":"beppobert","email":"philipp.dehler@googlemail.com"},"directories":{},"maintainers":[{"name":"beppobert","email":"philipp.dehler@googlemail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ts-combinator_0.0.1_1706534234273_0.3869460010506629"},"_hasShrinkwrap":false}},"time":{"created":"2024-01-29T13:17:14.198Z","0.0.1":"2024-01-29T13:17:14.496Z","modified":"2024-01-29T13:17:14.758Z"},"maintainers":[{"name":"beppobert","email":"philipp.dehler@googlemail.com"}],"description":"This library is a proof of concept and is not intended for production use. It aims to align an HKT implementation with runtime code. It implements a simple parser combinator library. You can use it to implement a typesafe JSON parser or GraphQL query pars","homepage":"https://github.com/Beppobert/ts-combinator#readme","keywords":["typescript","hkt","parser","combinator","parser-combinator"],"repository":{"type":"git","url":"git+https://github.com/Beppobert/ts-combinator.git"},"author":{"name":"Philipp Dehler"},"bugs":{"url":"https://github.com/Beppobert/ts-combinator/issues"},"license":"ISC","readme":"# Introduction\n\nThis library is a proof of concept and is not intended for production use. It aims to align an HKT implementation with runtime code. It implements a simple parser combinator library. You can use it to implement a typesafe JSON parser or GraphQL query parser.\n\n## Quickstart\n\n```shell\nnpm install @beppo/ts-combinator\n```\n\n### Example of a non-recursive array parser\n\n```typescript\nimport {\n  seq,\n  capture,\n  many,\n  or,\n  literal,\n  num,\n  to_number,\n  until,\n} from \"@beppo/ts-combinator\";\n\nconst number = capture(num, to_number);\nconst numberResult = number().apply(\"123\").parse();\n// numberResult = { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [123] }\nconst string = seq([literal(\"'\"), capture(until('\"')), literal(\"'\")]);\nconst stringResult = string().apply(\"'hello'\").parse();\n// stringResult = { tag:\"Ok\", pares: \"'hello'\", rest: \"\", stack: [\"hello\"] }\nconst value = or([number, string]);\nconst array = layer(\n  seq([\n    l_bracket,\n    optional(or([seq([one_or_more(seq([value, comma])), value]), value])),\n    r_bracket,\n  ])\n);\nconst arrayResult = array().apply(\"[1,2,3]\").parse();\n// arrayResult = { tag:\"Ok\", pares: \"[1,2,3]\", rest: \"\", stack: [[1,2,3]] }\n```\n\n## Example of a recursive array parser\n\nFor a recursive parser, an explicit type annotation is needed. To achieve this, we create a new type for the array parser and the value parser and wrap both in a function that returns the type. This is necessary because TypeScript can't infer recursive types. The neat part is that you just need to copy your implementation and replace every \"(\" and \")\" with \"<\" and \">\" to create the correct type annotation.\n\n```typescript\nimport {\n  seq,\n  capture,\n  many,\n  or,\n  literal,\n  num,\n  to_number,\n  until,\n} from \"@beppo/ts-combinator\";\n\nconst number = capture(num, to_number);\nconst numberResult = number().apply(\"123\").parse();\n// numberResult = { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [123] }\nconst string = seq([literal(\"'\"), capture(until('\"')), literal(\"'\")]);\nconst stringResult = string().apply(\"'hello'\").parse();\n// stringResult = { tag:\"Ok\", pares: \"'hello'\", rest: \"\", stack: [\"hello\"] }\n\ntype array = layer<\n  seq<\n    [\n      l_bracket,\n      optional<\n        or<[seq<[one_or_more<seq<[value, literal<\",\">]>>, value]>, value]>\n      >,\n      r_bracket\n    ]\n  >\n>;\nfunction array(): ReturnType<array> {\n  return layer(\n    seq([\n      l_bracket,\n      optional(\n        or([seq([one_or_more(seq([value, literal(\",\")])), value]), value])\n      ),\n      r_bracket,\n    ])\n  )();\n}\ntype value = or<[number, string, array]>;\nfunction value(): ReturnType<value> {\n  return or([number, string, array])();\n}\nconst arrayResult = array().apply('[1,2,3,[\"foo\",\"bar\",1]]').parse();\n// arrayResult = { tag:\"Ok\", pares: '[1,2,3,[\"foo\",\"bar\",1]]', rest: \"\", stack: [[1,2,3,[\"foo\",\"bar\",1]]] }\n```\n\n## Combinator API Documentation\n\n### literal\n\nThe literal combinator matches a string literal.\n\n```typescript\nconst singleQuote = literal(\"'\");\nsingleQuote().apply(\"'\").parse();\n// {tag:\"Ok\", pares: \"'\", rest: \"\", stack: [] }\nsingleQuote().apply(\"a\").parse();\n// {tag:\"Err\", error: \"[literal]: Expected: '. Received: 'a'\" }\n```\n\n### eoi (End of Input)\n\nThe eoi combinator matches the end of the input.\n\n```typescript\nconst end = eoi();\nend().apply(\"\").parse();\n// {tag:\"Ok\", pares: \"\", rest: \"\", stack: [] }\nend().apply(\"a\").parse();\n// {tag:\"Err\", error: \"[eoi]: Expected: End of Input. Received: 'a'\" }\n```\n\n### many\n\nThe many combinator matches the given parser zero or more times.\n\n```typescript\nconst manyA = many(literal(\"a\"));\nmanyA().apply(\"aaa\").parse();\n// {tag:\"Ok\", pares: \"aaa\", rest: \"\", stack: [] }\nmanyA().apply(\"b\").parse();\n// {tag:\"Ok\", pares: \"\", rest: \"b\", stack: [] }\n```\n\n### one_or_more\n\nThe one_or_more combinator matches the given parser one or more times.\n\n```typescript\nconst oneOrMoreA = one_or_more(literal(\"a\"));\noneOrMoreA().apply(\"aaa\").parse();\n// {tag:\"Ok\", pares: \"aaa\", rest: \"\", stack: [] }\noneOrMoreA().apply(\"b\").parse();\n// {tag:\"Err\", error: \"\"[one_or_more]: Expected: One or more. Received: 'b'\"\n```\n\n### optional\n\nThe optional combinator matches the given parser zero or one times.\n\n```typescript\nconst optionalA = optional(literal(\"a\"));\noptionalA().apply(\"a\").parse();\n// {tag:\"Ok\", pares: \"a\", rest: \"\", stack: [] }\noptionalA().apply(\"b\").parse();\n// {tag:\"Ok\", pares: \"\", rest: \"b\", stack: [] }\n```\n\n### seq\n\nThe seq combinator matches the given parsers in sequence.\nNote: The seq combinator propagates the error of the first failed parser.\n\n```typescript\nconst seqA = seq([literal(\"a\"), literal(\"b\")]);\nseqA().apply(\"ab\").parse();\n// {tag:\"Ok\", pares: \"ab\", rest: \"\", stack: [] }\nseqA().apply(\"a\").parse();\n// {tag:\"Err\", error: \"[literal]: Expected: b. Received: ''\" }\n```\n\n### or\n\nThe or combinator matches the given parsers in sequence.\n\n```typescript\nconst orA = or([literal(\"a\"), literal(\"b\")]);\norA().apply(\"a\").parse();\n// { tag:\"Ok\", pares: \"a\", rest: \"\", stack: [] }\norA().apply(\"b\").parse();\n// { tag:\"Ok\", pares: \"b\", rest: \"\", stack: [] }\norA().apply(\"c\").parse();\n// { tag:\"Err\", error: \"[or]: Expected: Matching combinator. Received: 'c'\" }\n```\n\n### until\n\nThe until combinator matches the given parser until the given literal matches.\nNote: There is no until combinator that takes a combinator as input.\n\n```typescript\nconst untilA = until(\"a\");\nuntilA().apply(\"fooooooa\").parse();\n// { tag:\"Ok\", pares: \"foooooo\", rest: \"a\", stack: [] }\n\nconst untilFail = until(\"a\");\nuntilFail().apply(\"foooooo\").parse();\n// { tag:\"Err\", error: \"[until]: Expected: a. Received: 'foooooo'\" }\n```\n\n## captures\n\nThe capture combinator pushes the result of the given parser to the stack.\n\n```typescript\nconst captureA = capture(literal(\"a\"));\ncaptureA().apply(\"a\").parse();\n// { tag:\"Ok\", pares: \"a\", rest: \"\", stack: [\"a\"] }\nconst captureAB = capture(seq([literal(\"a\"), literal(\"b\")]));\ncaptureAB().apply(\"ab\").parse();\n// { tag:\"Ok\", pares: \"ab\", rest: \"\", stack: [\"ab\"] }\n\nconst captureMultiple = capture(capture(literal(\"a\")));\ncaptureMultiple().apply(\"a\").parse();\n// { tag:\"Ok\", pares: \"a\", rest: \"\", stack: [\"a\",\"a\"] }\n```\n\nThere are also some custom mapper functions that can be used to transform captured values.\n\n### capture_with_label\n\nThe capture_with_label combinator pushes the result of the given parser to the stack with the given label.\n\n```typescript\nconst captureA = capture_with_label(\"a\", literal(\"a\"));\ncaptureA().apply(\"a\").parse();\n// { tag:\"Ok\", pares: \"a\", rest: \"\", stack: [{a:\"a\"}] }\nconst captureAB = capture_with_label(\"ab\", seq([literal(\"a\"), literal(\"b\")]));\ncaptureAB().apply(\"ab\").parse();\n// { tag:\"Ok\", pares: \"ab\", rest: \"\", stack: [{ab:\"ab\"}] }\n```\n\n### layer\n\nThe layer combinator encapsulates all matching combinators in a new stack layer.\n\n```typescript\nconst layerA = layer(literal(\"a\")); // no combinator\nlayerA().apply(\"a\").parse();\n// { tag:\"Ok\", pares: \"a\", rest: \"\", stack: [[]] } <- empty layer\n\nconst layerAB = layer(capture(seq([literal(\"a\"), literal(\"b\")])));\nlayerAB().apply(\"ab\").parse();\n// { tag:\"Ok\", pares: \"ab\", rest: \"\", stack: [[\"ab\"]] } <- layer with captured value\n```\n\n## Transformators\n\nTransformators are functions that transform the captured value or a stack layer.\nAt this point, if a transformation fails, it will result in a runtime error and the inference will behave unexpectedly.\n\n### to_number\n\nThe to_number transformator transforms the captured value to a number.\n\n```typescript\nimport { num, to_number, capture } from \"@beppo/ts-combinator\";\nconst number = capture(num, to_number);\nnumber().apply(\"123\").parse();\n// { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [123] }\n```\n\n### identity\n\nThe identity transformator returns the captured value.\n\n```typescript\nimport { num, identity, capture } from \"@beppo/ts-combinator\";\nconst number = capture(num, identity);\nnumber().apply(\"123\").parse();\n// { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [\"123\"] }\n```\n\n### constant\n\nThe constant transformator returns the given value.\n\n```typescript\nimport { num, constant, capture } from \"@beppo/ts-combinator\";\nconst number = capture(num, constant(42));\nnumber().apply(\"123\").parse();\n// { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [42] }\n```\n\nIf you don't want to infer the literal value, there is an expand util that can be used to widen the type of the literal.\n\n```typescript\nimport { num, constant, capture, expand } from \"@beppo/ts-combinator\";\nconst number = capture(num, constant(expand(42)));\nnumber().apply(\"123\").parse();\n// { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [number] }\n```\n\n### widen\n\nThe widen transformator widens the return type of another transformator.\n\n```typescript\nimport { num, widen, capture } from \"@beppo/ts-combinator\";\nconst number = capture(num, widen(to_number));\nnumber().apply(\"123\").parse();\n// { tag:\"Ok\", pares: \"123\", rest: \"\", stack: [number] }\n```\n\n### lookup\n\nThe lookup transformator looks up the given key from a known dictionary.\nThe dictionary needs a \"Default\" key.\n\n```typescript\nimport { num, lookup, capture } from \"@beppo/ts-combinator\";\nconst foo = literal(\"foo\");\nconst bar = literal(\"bar\");\nconst fizz = literal(\"fizz\");\n\nconst lookedUp = capture(\n  or([foo, bar, fizz]),\n  lookup({ foo: 42, bar: 43, Default: 44 })\n);\nlookedUp().apply(\"foo\").parse();\n// { tag:\"Ok\", pares: \"foo\", rest: \"\", stack: [42] }\nlookedUp().apply(\"bar\").parse();\n// { tag:\"Ok\", pares: \"bar\", rest: \"\", stack: [43] }\nlookedUp().apply(\"fizz\").parse();\n// { tag:\"Ok\", pares: \"fizz\", rest: \"\", stack: [44] }\n```\n\n### from_entries\n\nThe from_entries transformator transforms an array of entries into an object.\n\n```typescript\nimport { num, from_entries, capture } from \"@beppo/ts-combinator\";\nconst str = seq([literal('\"'), capture(until('\"')), literal('\"')]);\nconst record_string_string = layer(\n  seq([\n    l_brace,\n    or([seq([one_or_more(seq([str, comma])), str]), optional(str)]),\n    r_brace,\n  ]),\n  from_entries\n)();\n\nrecord_string_string().apply('{\"foo\":\"bar\"}').parse();\n// { tag:\"Ok\", pares: '{\"foo\":\"bar\"}', rest: \"\", stack: [{foo:\"bar\"}] }\nrecord_string_string().apply('{\"foo\":\"bar\",\"fizz\":\"buzz\"}').parse();\n// { tag:\"Ok\", pares: '{\"foo\":\"bar\",\"fizz\":\"buzz\"}', rest: \"\", stack: [{foo:\"bar\",fizz:\"buzz\"}] }\n```\n\n## Error handling\n\nMost of the combinator functions have a second parameter for a custom error message.\n\nYour custom error message can be enriched with the expected and received values and the name of the combinator by using the string formats \"%e\" and \"%r\" and \"%c\".\n\n```typescript\nconst literalA = literal(\"a\", \"Expected: %e. Received: %r. Combinator: %c\");\nliteralA().apply(\"b\").parse();\n// { tag:\"Err\", error: \"[literal]: Expected: a. Received: 'b'. Combinator: literal\" }\n```\n\n## Custom combinators\n\nYou can create your own combinators. Take this dummy code example\n\n```typescript\nimport { Combinator, Ok, Err, isOk } from \"@beppo/ts-combinator\";\n\ntype _FooCombinator<Input extends string> = // /.../ <- your combinator as type\n\nclass FooCombinator extends Combinator<\"some-name\"> {\n  constructor(private readonly combinator: C) {\n    super(\"some-name\");\n  }\n  parse():_FooCombinator<this[\"arg\"]> //<- use this[\"arg\"] to get the input type\n  {\n    const input = this.arg // <- use this.arg to get the input\n\n    /**\n     * reimplement the parse function from _FooCombinator type\n    */\n   return ok(parsed, rest, stack) as any // <- return Ok or Err\n  }\n}\n\n// to prevent unexpected bahaviour at runtime you should create a lazy function that returns the combinator\n\nfunction fooCombinator() {\n  return new FooCombinator();\n}\n// or if you have some input\nfunction fooCombinator(input:SomeOtherCombinator): FooCombinator {\n  return ()=>new FooCombinator(input);\n}\n\n```\n\n## Custom transformators\n\n```typescript\nimport { Lazy, MapCapture } from \"@beppo/ts-combinator\";\n\n// create a type that matches your needs\ntype _ToNumber<T> = T extends `${infer N extends number}` ? N : never;\n\n// create a class that extends MapCapture\nexport class ToNumber extends MapCapture<\"to_number\"> {\n  constructor() {\n    super(\"to_number\" as const);\n  }\n  // create a \"map\" function that returns the type you created above\n  // it will take this[\"arg\"] as input\n  map(): _ToNumber<this[\"arg\"]> {\n    // the map function body should match the type implementation you created above\n    const parsed = Number(this.arg);\n    if (isNaN(parsed)) {\n      throw new Error(`Expected a number. Received: ${this.arg}`);\n    }\n    return parsed as any;\n  }\n}\n// create a lazy function that returns the transformator\nexport type to_number = Lazy<ToNumber>;\nexport function to_number(): ReturnType<to_number> {\n  return new ToNumber();\n}\n```\n\n## Json parser example\n\n```typescript\nimport {\n  seq,\n  capture,\n  many,\n  or,\n  literal,\n  num,\n  to_number,\n  until,\n  one_or_more,\n  optional,\n  layer,\n  from_entries,\n  expand,\n  constant,\n} from \"@beppo/ts-combinator\";\n\ntype json_string = seq<[quote, capture<until_quote>, quote]>;\nfunction json_string(): ReturnType<json_string> {\n  return seq([quote, capture(until_quote), quote])();\n}\n\ntype json_null = capture<literal<\"null\">, constant<null>>;\nfunction json_null(): ReturnType<json_null> {\n  return capture(literal(\"null\"), constant(null))();\n}\ntype json_true = capture<literal<\"true\">, constant<boolean>>;\n\nfunction json_true(): ReturnType<json_true> {\n  return capture(literal(\"true\"), constant(expand(true)))();\n}\ntype json_false = capture<literal<\"false\">, constant<boolean>>;\nfunction json_false(): ReturnType<json_false> {\n  return capture(literal(\"false\"), constant(expand(false)))();\n}\ntype json_number = capture<typeof num, to_number>;\nfunction json_number(): ReturnType<json_number> {\n  return capture(num, to_number)();\n}\n\ntype key_value = layer<seq<[json_string, colon, json]>>;\nfunction key_value() {\n  return layer(seq([json_string, colon, json]))();\n}\n\ntype json_object = layer<\n  seq<\n    [\n      l_brace,\n      or<\n        [\n          seq<[one_or_more<seq<[key_value, comma]>>, key_value]>,\n          optional<key_value>\n        ]\n      >,\n      r_brace\n    ]\n  >,\n  from_entries\n>;\n\nfunction json_object(): ReturnType<json_object> {\n  return layer(\n    seq([\n      l_brace,\n      or([\n        seq([one_or_more(seq([key_value, comma])), key_value]),\n        optional(key_value),\n      ]),\n      r_brace,\n    ]),\n    from_entries\n  )();\n}\n\ntype json_array = layer<\n  seq<\n    [\n      l_bracket,\n      optional<or<[seq<[one_or_more<seq<[json, comma]>>, json]>, json]>>,\n      r_bracket\n    ]\n  >\n>;\nfunction json_array(): ReturnType<json_array> {\n  return layer(\n    seq([\n      l_bracket,\n      optional(or([seq([one_or_more(seq([json, comma])), json]), json])),\n      r_bracket,\n    ])\n  )();\n}\n\ntype json = or<\n  [\n    json_object,\n    json_array,\n    json_string,\n    json_null,\n    json_true,\n    json_false,\n    json_number\n  ]\n>;\nconst json = or([\n  json_object,\n  json_array,\n  json_string,\n  json_null,\n  json_true,\n  json_false,\n  json_number,\n]);\n\nconst jsonResult = json()\n  .apply(\n    '{\"foo\":\"bar\",\"fizz\":\"buzz\",\"arr\":[1,2,3],\"obj\":{\"foo\":\"bar\"},\"null\":null,\"true\":true,\"false\":false}'\n  )\n  .parse();\n// jsonResult = {\n//   tag: \"Ok\",\n//   pares: '{\"foo\":\"bar\",\"fizz\":\"buzz\",\"arr\":[1,2,3],\"obj\":{\"foo\":\"bar\"},\"null\":null,\"true\":boolean,\"false\":boolean}',\n```\n","readmeFilename":"ReadMe.md"}