{"id":29109574,"url":"https://github.com/doeixd/combi-parse","last_synced_at":"2025-10-10T07:19:53.260Z","repository":{"id":301229962,"uuid":"1008590325","full_name":"doeixd/combi-parse","owner":"doeixd","description":"Type-safe, friendly parser combinator library for TypeScript featuring composable parsers, left-recursion support, detailed error reporting, and Unicode character classes.","archived":false,"fork":false,"pushed_at":"2025-07-28T12:24:27.000Z","size":897,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-14T10:36:41.310Z","etag":null,"topics":["combinators","grammar","parser","parser-combinators","typescript","typescript-library"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/doeixd.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,"zenodo":null}},"created_at":"2025-06-25T19:30:23.000Z","updated_at":"2025-07-28T12:24:30.000Z","dependencies_parsed_at":"2025-06-25T20:41:21.911Z","dependency_job_id":null,"html_url":"https://github.com/doeixd/combi-parse","commit_stats":null,"previous_names":["doeixd/combi-parse"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/doeixd/combi-parse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fcombi-parse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fcombi-parse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fcombi-parse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fcombi-parse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doeixd","download_url":"https://codeload.github.com/doeixd/combi-parse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fcombi-parse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279003172,"owners_count":26083533,"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":["combinators","grammar","parser","parser-combinators","typescript","typescript-library"],"created_at":"2025-06-29T07:03:13.755Z","updated_at":"2025-10-10T07:19:53.250Z","avatar_url":"https://github.com/doeixd.png","language":"TypeScript","readme":"[![NPM Version](https://img.shields.io/npm/v/@doeixd/combi-parse.svg)](https://www.npmjs.com/package/@doeixd/combi-parse)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](#)\n[![TypeScript](https://img.shields.io/badge/%3C/%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)\n\n# Combi-Parse\n\nA friendly, powerful, and type-safe parser combinator library for TypeScript. It helps you transform raw, structured text into meaningful data with confidence and clarity.\n\nCombi-Parse is built on a simple but powerful idea: **parser combinators**. Think of them like Lego blocks. You start with tiny, simple parsers that do one thing well (like matching the word \"let\" or a number). You then \"combine\" these blocks to build bigger, more sophisticated parsers that can understand complex structures, like a programming language or a JSON file.\n\nThe library handles the complex details—like tracking the current position in the text, managing state, and reporting helpful errors—so you can focus on describing the *what* of your data's grammar, not the *how*.\n\n\u003cbr /\u003e\n\n## 📥 Installation\n\n```bash\nnpm install @doeixd/combi-parse\n```\n\n\u003cbr /\u003e\n\n## 🚀 Quick Start: A Guided Tour\n\nLet's build our first parser. Our goal is to parse a simple variable declaration string like `let user = \"jane\";` and turn it into a structured JavaScript object.\n\nWe'll build this up step-by-step to understand what’s happening.\n\n### Step 1: Understanding the \"Parser\"\n\nFirst, what *is* a parser in this library?\n\nA parser is an object that \"knows\" how to recognize a specific piece of text. It's not the *result* itself; it's the *machine* that produces the result. Every parser has a `.parse()` method that you run on an input string.\n\nLet's make the simplest possible parser: one that recognizes the exact word `let`.\n\n```typescript\nimport { str } from '@doeixd/combi-parse';\n\n// Create a parser that looks for the literal string 'let'.\nconst letParser = str('let');\n\n// Let's run it!\nconst result = letParser.parse('let there be light');\n\nconsole.log(result); // Output: 'let'\n\n// What happens if it fails?\ntry {\n  letParser.parse('const there be light');\n} catch (error) {\n  console.error(error.message); // Output: \"ParseError at 1:1, expected 'let' but got 'const...'\"\n}\n```\nAs you can see, a parser either successfully returns the value it parsed or throws a descriptive error.\n\n### Step 2: Handling Patterns and Whitespace\n\nHardcoding every string isn't enough. We need to parse things like variable names, which follow a pattern. For that, we use the `regex` parser.\n\nWe also need to handle whitespace. It would be annoying to manually parse spaces after every token. This is where `lexeme` comes in. A **lexeme** is a token followed by any insignificant trailing whitespace.\n\n`lexeme()` is a **higher-order parser**: it takes a parser as input and returns a *new* parser that does the original job and *also* consumes any whitespace that follows.\n\n```typescript\nimport { str, regex, lexeme } from '@doeixd/combi-parse';\n\n// `lexeme` wraps our basic parsers to also handle trailing whitespace.\n// This makes composing them much cleaner.\n\n// A parser for the 'let' keyword, ignoring any space after it.\nconst letKeyword = lexeme(str('let'));\n\n// A parser for a variable name using a regular expression.\nconst identifier = lexeme(regex(/[a-zA-Z_][a-zA-Z0-9_]*/));\n\n// A parser for the equals sign.\nconst equals = lexeme(str('='));\n\n// We don't need lexeme for the final semicolon, as there's no trailing space to consume.\nconst semicolon = str(';');\n```\n\n### Step 3: Parsing a Sequence of Things\n\nNow we have parsers for the individual pieces: `let`, `user`, `=`, and `;`. We need to tell Combi-Parse to run them in a specific order. For that, we use the `sequence` combinator.\n\n`sequence` takes an array of parsers and runs them one after another. If they all succeed, it returns an array of their results.\n\n```typescript\n// ... imports and parsers from above\n\n// Let's define a parser for a string literal like \"jane\".\n// The `between` parser is perfect for this. It parses whatever is\n// between a start and end token.\nconst stringLiteral = between(str('\"'), regex(/[^\"]*/), str('\"'));\n\n// Now, let's combine everything into a sequence.\nconst declarationParser = sequence([\n  letKeyword,\n  identifier,\n  equals,\n  stringLiteral,\n  semicolon\n]);\n\n// Run it on our input string.\nconst result = declarationParser.parse('let user = \"jane\";');\n\nconsole.log(result);\n// Output: [ 'let', 'user', '=', 'jane', ';' ]\n```\nIt worked! We got back an array of all the successfully parsed parts.\n\n### Step 4: Transforming the Result into Something Useful\n\nThe array `['let', 'user', '=', 'jane', ';']` is correct, but it’s not very useful. We want a clean object like `{ name: 'user', value: 'jane' }`.\n\nThe `sequence` combinator can take a second argument: a **mapper function**. This function receives the array of results and lets you transform it into any shape you want.\n\nThis is also where `as const` becomes incredibly useful. By adding `as const` to our array of parsers, we give TypeScript more precise information. It knows *exactly* what type is at each position in the array (e.g., the first element is a `string`, the second is a `string`, etc.), giving us perfect type-safety and autocompletion in our mapper function!\n\n### Final Code: Putting It All Together\n\n```typescript\nimport { str, regex, sequence, between, lexeme } from '@doeixd/combi-parse';\n\n// 1. Define parsers for the smallest pieces (tokens).\n// `lexeme` is a helper that wraps a parser to also consume trailing whitespace.\nconst letKeyword = lexeme(str('let'));\nconst identifier = lexeme(regex(/[a-zA-Z_][a-zA-Z0-9_]*/));\nconst equals = lexeme(str('='));\nconst semicolon = str(';');\n\n// A string literal is any text between double quotes.\nconst stringLiteral = between(str('\"'), regex(/[^\"]*/), str('\"'));\n\n// 2. Compose the small parsers into a larger one that understands a sequence.\nconst declarationParser = sequence(\n  // The list of parsers to run in order.\n  [\n    letKeyword,\n    identifier,\n    equals,\n    stringLiteral,\n    semicolon,\n  ] as const, // `as const` tells TypeScript to infer the exact shape of the results array.\n\n  // 3. Transform the raw results into a clean, structured object.\n  // We only care about the identifier (name) and the string literal (value).\n  // Because of `as const`, TypeScript knows `name` and `value` are strings!\n  ([_let, name, _eq, value, _semi]) =\u003e ({\n    type: 'declaration',\n    name,\n    value\n  })\n);\n\n// 4. Run it!\nconst result = declarationParser.parse('let user = \"jane\";');\n\n// The output is a perfectly typed and structured object.\nconsole.log(result);\n// Output: { type: 'declaration', name: 'user', value: 'jane' }\n```\nAnd there you have it! You've seen the core idea: **build big, powerful parsers by combining small, simple ones.**\n\n\u003cbr /\u003e\n\n## The Power of Composition: A Full JSON Parser\n\nYou can use these same building blocks to create a complete, robust parser for a complex format like JSON. This demonstrates how the simple ideas of `sequence`, `choice`, and `many` can scale up.\n\nA new concept here is `lazy()`. Since JSON can be recursive (an object can contain other objects), we need a way to reference a parser before it's fully defined. `lazy()` acts as a placeholder for this purpose.\n\n```typescript\nimport { str, regex, sequence, choice, between, many, sepBy, lazy, lexeme, Parser } from '@doeixd/combi-parse';\n\n// `lazy()` lets us define recursive parsers, since a `jsonValue`\n// can contain other `jsonValue`s (e.g., in an array or object).\nconst jsonValue: Parser\u003cany\u003e = lazy(() =\u003e choice([\n  str('null').map(() =\u003e null),\n  str('true').map(() =\u003e true),\n  str('false').map(() =\u003e false),\n  regex(/-?\\d+(\\.\\d+)?/).map(Number),\n  between(str('\"'), regex(/[^\"]*/), str('\"')),\n  jsonArray,  // A value can be an array...\n  jsonObject  // ...or an object.\n]));\n\nconst jsonString = between(str('\"'), regex(/[^\"]*/), str('\"'));\n\n// A property is a key-value pair, like \"name\": \"John\"\nconst jsonProperty = sequence(\n  [lexeme(jsonString), str(':'), jsonValue] as const,\n  ([key, , value]) =\u003e [key, value]\n);\n\n// An object is a comma-separated list of properties between curly braces.\nconst jsonObject = between(\n  lexeme(str('{')),\n  sepBy(jsonProperty, lexeme(str(','))),\n  str('}')\n).map(pairs =\u003e Object.fromEntries(pairs));\n\n// An array is a comma-separated list of values between square brackets.\nconst jsonArray = between(\n  lexeme(str('[')),\n  sepBy(jsonValue, lexeme(str(','))),\n  str(']')\n);\n\n// Run the final parser on a complex JSON string.\nconst parsed = jsonValue.parse('{\"users\": [{\"id\": 1, \"name\": \"Alice\"}]}');\nconsole.log(parsed.users[0].name); // \"Alice\"\n```\nThis parser is readable, reusable, and type-safe. Each component can be tested and used independently.\n\n\u003cbr /\u003e\n\n## ✨ Core Philosophy\n\nWe designed Combi-Parse around a few key principles to make parsing a better experience.\n\n*   ✅ **Type-Safety First**: The library leverages TypeScript's type system to the fullest. You get precise type inference and compile-time validation, so if your grammar changes, your code will tell you what needs fixing.\n\n*   🧩 **Radical Composability**: Every parser is a small, reusable component. This lets you build incredibly complex grammars from simple, testable pieces. A parser for a `number` can be used in a parser for a `date`, which can be used in a parser for a `log file`.\n\n*   📍 **Human-Friendly Errors**: Say goodbye to `undefined is not a function`. Combi-Parse gives you precise error locations with line and column numbers, along with contextual messages that tell you *what* the parser was trying to do when it failed.\n\n*   🛠️ **A Tool for Every Task**: Real-world parsing is more than just text. Combi-Parse provides specialized toolkits for different domains, so you always have the right tool for the job.\n\n\u003cbr /\u003e\n\n\u003cbr/\u003e\n\n## ✨ Principles \u0026 Capabilities\n\nYou've seen the basics of combining parsers. Now, let's explore what makes Combi-Parse uniquely suited for real-world scenarios. The library is built on a few core principles that address common parsing challenges.\n\n### 1. Precise Type-Safety: Catch Errors Before You Run\n\nWhile many tools might return a generic `string[]` or `any`, Combi-Parse is designed to give you the most precise types possible. This allows TypeScript to catch logical errors in your parsing code at compile time, long before it ever runs.\n\nA key pattern is using `as const` with the `sequence` combinator. This gives TypeScript enough information to infer the exact type of each element in your parsed sequence.\n\n```typescript\nimport { sequence, str, regex, number } from '@doeixd/combi-parse';\n\nconst declaration = sequence(\n  [\n    str('let'),\n    regex(/[a-z]+/), // Our identifier\n    str('='),\n    number,           // Our value\n  ] as const, // `as const` helps TypeScript infer the exact tuple type\n  \n  // Because of `as const`, TypeScript knows the exact type of each element.\n  ([_let, name, _eq, value]) =\u003e ({ name, value })\n);\n\nconst result = declaration.parse('let age = 42');\n\n// No more guesswork.\n// result.name is known to be a `string`.\n// result.value is known to be a `number`.\nconsole.log(`Variable '${result.name}' has value ${result.value}.`);\n```\n\n### 2. First-Class Composability: Build Reusable Grammars\n\nIn Combi-Parse, parsers are first-class values. You can store them in variables, pass them to functions, and compose them in flexible and powerful ways. This enables you to create **higher-order parsers**—functions that build new parsers—to eliminate boilerplate and make your grammars more modular and maintainable.\n\nFor example, you can create a function that wraps any parser to add a timestamp to its result:\n\n```typescript\nimport { Parser, regex } from '@doeixd/combi-parse';\n\n// A higher-order parser that takes any parser and enhances its result.\nfunction withTimestamp\u003cT\u003e(parser: Parser\u003cT\u003e): Parser\u003c{ data: T, timestamp: number }\u003e {\n  return parser.map(data =\u003e ({\n    data,\n    timestamp: Date.now()\n  }));\n}\n\n// Now, apply it to an existing parser to create a new, timestamp-aware one.\nconst timestampedErrorLog = withTimestamp(regex(/ERROR: .*/));\n\nconst log = timestampedErrorLog.parse('ERROR: DB connection failed');\n// log -\u003e { data: \"ERROR: DB connection failed\", timestamp: 167... }\n```\n\n### 3. Generator Syntax: Clean Code for Complex Sequences\n\nDeeply nested `sequence` and `.chain()` calls can become hard to read. For complex, multi-step parsing, you can use `genParser` to write your logic in a clean, imperative style that looks just like standard synchronous code, improving readability and maintainability.\n\n```typescript\nimport { genParser, anyOf, str, regex } from '@doeixd/combi-parse/generator';\n\nconst httpHeaderParser = genParser(function* () {\n  const method = yield anyOf(['GET', 'POST', 'PUT'] as const);\n  yield str(' ');\n  const path = yield regex(/[\\w\\/]+/);\n  yield str(' HTTP/1.1\\r\\n');\n  \n  // This style makes it easy to add loops for parsing multiple header lines.\n  \n  return { method, path };\n});\n\nconst header = httpHeaderParser.parse('GET /api/users HTTP/1.1\\r\\n...');\n// header -\u003e { method: 'GET', path: '/api/users' }\n```\n\n### 4. Binary Parsing: Go Beyond Text\n\nParsing isn't just for text. With the dedicated binary toolkit, you can apply the same combinator approach to decode file formats, network protocols, or any structured binary data. It handles details like endianness and data views automatically.\n\n```typescript\nimport { Binary } from '@doeixd/combi-parse/binary';\n\n// A parser for a simple image header.\nconst imageHeaderParser = Binary.sequence([\n  Binary.u32BE,        // Width, 4 bytes, big-endian\n  Binary.u32BE,        // Height, 4 bytes, big-endian\n  Binary.u8,           // Color depth (1 byte)\n  Binary.string(3),    // Image type, e.g., \"IMG\" (3 bytes)\n] as const).map(([width, height, depth, type]) =\u003e ({\n  width, height, depth, type\n}));\n\n// Create a sample buffer: 1024x768, 8-bit depth, \"IMG\"\nconst buffer = new Uint8Array([\n  0, 0, 4, 0,  // 1024\n  0, 0, 3, 0,  // 768\n  8,           // 8\n  73, 77, 71   // \"IMG\"\n]).buffer;\n\nconst header = imageHeaderParser.parse(buffer, 0);\n// header -\u003e { width: 1024, height: 768, depth: 8, type: 'IMG' }\n```\n\n### 5. Stream Processing: Handle Large-Scale Data\n\nWhat if your input is a 10GB log file or a real-time data feed? Loading it all into memory is not an option. The stream processing engine allows you to parse enormous datasets chunk-by-chunk with constant memory usage.\n\n```typescript\nimport { createStreamSession, lift } from '@doeixd/combi-parse/stream';\nimport { jsonObjectParser } from './my-json-parser'; // Assume a JSON object parser\nimport { whitespace } from '@doeixd/combi-parse';\n\n// Create a session to parse a stream of JSON objects separated by newlines.\nconst session = createStreamSession(\n  lift(jsonObjectParser), // Parser for one item\n  lift(whitespace)        // Delimiter between items\n);\n\n// Process each item as it's parsed.\nsession.on('item', ({ value: log }) =\u003e {\n  if (log.level === 'ERROR') {\n    // A security alert was logged.\n  }\n});\n\n// Feed data chunks as they arrive from a file or network.\nsession.feed('{\"level\":\"INFO\", \"msg\":\"User logged in\"}\\n{\"level\":\"ERROR\"');\nsession.feed(', \"msg\":\"DB connection failed\"}\\n'); // Handles incomplete chunks automatically\n\nsession.end();\n```\n\n### 6. Advanced Engines: Built for Demanding Applications\n\nCombi-Parse includes specialized engines for the most demanding use cases, ensuring you have the right tool for any job.\n\n-   **Incremental Parsing:** For applications like **code editors and IDEs**, this engine can re-parse a document after a small text change by reusing unchanged parts of the parse tree. This enables near-instant feedback for features like live error checking and syntax highlighting.\n    ```typescript\n    // In an editor environment:\n    const session = createIncrementalSession(myLanguageParser);\n    await session.parse(initialDocument);\n\n    // When the user types 'x':\n    await session.parse(newDocument, [{ range, text: 'x' }]); // Re-parses in milliseconds\n    ```\n\n-   **Secure Parsing:** When parsing **untrusted user input**, a cleverly crafted string can cause some parsers to enter an infinite loop or use exponential amounts of memory. The secure parsing engine runs your parser in a sandbox with resource limits.\n    ```typescript\n    const safeParser = createSecureSession(myParser, {\n      maxDepth: 50,         // Limit recursion to prevent stack overflows\n      maxParseTime: 1000,   // Timeout after 1 second\n    });\n    \n    try {\n      safeParser.parse(maliciousUserInput);\n    } catch (e) {\n      console.log('A security violation was caught.');\n    }\n    ```\n\u003cbr /\u003e\n\n## 🧰 A Tool for Every Task: Parsing Paradigms\n\nCombi-Parse gives you a toolkit of specialized approaches so you can choose the right one for your project.\n\n| Paradigm | Best For... | Example Import |\n| :--- | :--- | :--- |\n| **Traditional Combinators** | General parsing, functional style | `import { sequence } from '@doeixd/combi-parse';` |\n| **Generator-Based Parsing** | Complex, multi-step, or stateful logic | `import { genParser } from '@doeixd/combi-parse';` |\n| **Binary Data Parsing** | File formats, network protocols | `import { Binary } from '@doeixd/combi-parse/binary';` |\n| **Stream Processing** | Large files, real-time data feeds | `import { createStreamParser } from '@doeixd/combi-parse/stream';` |\n| **Incremental Parsing** | Code editors, IDEs, live previews | `import { IncrementalParser } from '@doeixd/combi-parse/incremental';` |\n| **Secure Parsing** | Untrusted user input, API endpoints | `import { SecureParser } from '@doeixd/combi-parse/secure';` |\n| **Type-Level Regex** | Compile-time validation, type-safe patterns | `import type { CompileRegex } from '@doeixd/combi-parse/regex';` |\n\n\u003cbr /\u003e\n\n## 📖 Documentation \u0026 Learning\n\nReady to build your own parser? We have comprehensive documentation to guide you.\n\n| To... | See... |\n| :--- | :--- |\n| **Understand the fundamentals** | **[Core Concepts](docs/core-concepts.md)** |\n| **Follow a guided example** | **[Tutorial: Your First Parser](docs/tutorial.md)** |\n| **See a real-world example** | **[Complete JSON Parser Example](docs/examples/json.md)** |\n| **Choose the right tools** | **[API Overview](docs/api/overview.md)** |\n| **Handle tricky situations** | **[Advanced Techniques](docs/advanced-techniques.md)** |\n| **Parse a binary file format** | **[Binary Parsing Guide](docs/binary.md)** |\n| **Handle a real-time data feed**| **[Async \u0026 Streaming Guide](docs/async-streaming.md)** |\n| **Write a type-safe regex** | **[Type-Level Regex Guide](docs/regex-and-type-safety.md)** |\n| **Tune for speed** | **[Performance Guide](docs/performance.md)** |\n| **Fix a common problem** | **[Troubleshooting Guide](docs/troubleshooting.md)** |\n\n\u003cbr /\u003e\n\n# API Reference\n\nA guide to the Combi-Parse library, organized by module and functionality.\n\n### Primitive Parsers\n\nThese are the fundamental building blocks for recognizing basic patterns.\n\n#### String \u0026 Pattern Matching\n\n**str(text: string)** → `Parser\u003cstring\u003e`\nMatches the exact string `text`.\n```javascript\nstr(\"let\") // matches \"let\" exactly\n```\n\u003cbr /\u003e\n\n**regex(pattern: RegExp)** → `Parser\u003cstring\u003e`\nMatches a regular expression. The pattern is automatically anchored to the current position.\n```javascript\nregex(/\\d+/) // matches one or more digits\n```\n\u003cbr /\u003e\n\n**charClass(...classes)** → `Parser\u003cstring\u003e`\nMatches a single character from a type-safe class (e.g., `'Digit'`) or a custom string.\n```javascript\ncharClass('Digit') // matches 0-9\ncharClass('aeiou') // matches vowels\n```\n\u003cbr /\u003e\n\n**anyOf(strings: readonly string[])** → `Parser\u003cT[number]\u003e`\nMatches any of the provided literal strings. A type-safe and ergonomic alternative to `choice`.\n```javascript\nanyOf(['GET', 'POST'] as const) // matches HTTP methods\n```\n\u003cbr /\u003e\n\n#### Character \u0026 Number Parsing\n\n**number** → `Parser\u003cnumber\u003e`\nParses an integer or floating-point number.\n```javascript\nnumber // matches \"123\", \"3.14\", \"-42\"\n```\n\u003cbr /\u003e\n\n**anyChar** → `Parser\u003cstring\u003e`\nConsumes and returns any single character. Fails only at the end of input.\n\n**noneOf(chars: string)** → `Parser\u003cstring\u003e`\nMatches any single character that is *not* in the `chars` string.\n```javascript\nnoneOf('()[]{}') // matches any char except brackets\n```\n\u003cbr /\u003e\n\n**whitespace** → `Parser\u003cstring\u003e`\nMatches one or more whitespace characters (`\\s+`).\n\n#### Control Flow\n\n**succeed(value: T)** → `Parser\u003cT\u003e`\nAlways succeeds with the given `value`, consuming no input. Useful for injecting defaults.\n\n**fail(message: string)** → `Parser\u003cnever\u003e`\nAlways fails with the given `message`, consuming no input. For semantic validation.\n\n**eof** → `Parser\u003cnull\u003e`\nSucceeds only at the very end of the input string, ensuring it was all consumed.\n\n### Combinator Functions\n\nThese higher-order functions assemble simple parsers into more complex ones.\n\n#### Sequence \u0026 Choice\n\n**sequence(parsers: Parser[], mapper?: Function)** → `Parser\u003cany[] | U\u003e`\nRuns parsers in order. Returns an array of results, or a transformed value via the optional `mapper`.\n```javascript\nsequence([str('('), number, str(')')], ([, num]) =\u003e num)\n```\n\u003cbr /\u003e\n\n**choice(parsers: Parser[])** → `Parser\u003cT\u003e`\nTries parsers in order, returning the first success. Provides intelligent, combined error messages.\n```javascript\nchoice([str('true'), str('false'), number])\n```\n\u003cbr /\u003e\n\n#### Repetition\n\n**many(parser: Parser\u003cT\u003e)** → `Parser\u003cT[]\u003e`\nMatches the `parser` zero or more times. Returns an array of results. Never fails.\n```javascript\nmany(regex(/\\w/)) // matches zero or more word characters\n```\n\u003cbr /\u003e\n\n**many1(parser: Parser\u003cT\u003e)** → `Parser\u003cT[]\u003e`\nMatches the `parser` one or more times. Fails if it can't match at least once.\n\n**count(n: number, parser: Parser\u003cT\u003e)** → `Parser\u003cT[]\u003e`\nMatches the `parser` exactly `n` times.\n```javascript\ncount(3, regex(/\\d/)) // matches exactly 3 digits\n```\n\u003cbr /\u003e\n\n#### Lists \u0026 Separators\n\n**sepBy(item: Parser\u003cT\u003e, sep: Parser\u003cU\u003e)** → `Parser\u003cT[]\u003e`\nMatches zero or more `item`s separated by `sep`. Ideal for lists like `1,2,3`.\n```javascript\nsepBy(number, str(',')) // matches \"1,2,3\" or \"\"\n```\n\u003cbr /\u003e\n\n**sepBy1(item: Parser\u003cT\u003e, sep: Parser\u003cU\u003e)** → `Parser\u003cT[]\u003e`\nMatches one or more `item`s separated by `sep`.\n```javascript\nsepBy1(number, str(',')) // matches \"1,2,3\" but not \"\"\n```\n\u003cbr /\u003e\n\n#### Delimiters \u0026 Structure\n\n**between(left: Parser\u003cL\u003e, content: Parser\u003cC\u003e, right: Parser\u003cR\u003e)** → `Parser\u003cC\u003e`\nMatches `content` surrounded by `left` and `right` delimiters.\n```javascript\nbetween(str('('), number, str(')')) // matches \"(42)\"\n```\n\u003cbr /\u003e\n\n**until(terminator: Parser\u003cT\u003e)** → `Parser\u003cstring\u003e`\nConsumes characters as a string until the `terminator` parser succeeds. Perfect for comments or string contents.\n```javascript\nuntil(str('*/')) // matches everything until \"*/\"\n```\n\u003cbr /\u003e\n\n#### Advanced Combinators\n\n**lazy(fn: () =\u003e Parser\u003cT\u003e)** → `Parser\u003cT\u003e`\nDefers parser creation. **Essential for recursive grammars** (e.g., a JSON value parser).\n```javascript\nconst jsonValue = lazy(() =\u003e choice([jsonObject, jsonArray, str('null')]))\n```\n\u003cbr /\u003e\n\n### Parser Class Methods\n\nThese methods can be chained onto any parser instance for a fluent-style API.\n\n#### Transformation\n\n**.map(fn: (value: T) =\u003e U)** → `Parser\u003cU\u003e`\nTransforms a parser's successful result. The most common way to shape your output data.\n```javascript\nregex(/\\d+/).map(Number) // parse digits and convert to number\n```\n\u003cbr /\u003e\n\n**.tryMap(fn: (value: T) =\u003e Result\u003cU\u003e)** → `Parser\u003cU\u003e`\nTransforms a result using a function that can *also fail*. Used for semantic validation after a successful parse.\n```javascript\nnumber.tryMap(n =\u003e n \u003c 256 ? success(n) : fail('too large'))\n```\n\u003cbr /\u003e\n\n**.chain(fn: (value: T) =\u003e Parser\u003cU\u003e)** → `Parser\u003cU\u003e`\nSequences another parser where the next logic depends on the result of the first. The most powerful way to create dynamic parsers.\n```javascript\nstr('repeat').chain(() =\u003e number).chain(n =\u003e count(n, anyChar))\n```\n\u003cbr /\u003e\n\n#### Alternatives \u0026 Options\n\n**.or(other: Parser\u003cU\u003e)** → `Parser\u003cT | U\u003e`\nProvides an alternative `other` parser if the first one fails *without consuming input*.\n```javascript\nstr('yes').or(str('no')) // matches either \"yes\" or \"no\"\n```\n\u003cbr /\u003e\n\n**.optional()** → `Parser\u003cT | null\u003e`\nMakes a parser optional. Succeeds with `null` if the parser would have failed.\n```javascript\nstr('const').optional() // matches \"const\" or nothing\n```\n\u003cbr /\u003e\n\n#### Sequencing\n\n**.keepLeft(other: Parser\u003cU\u003e)** → `Parser\u003cT\u003e`\nRuns `other` parser after, but keeps the result of the first one.\n```javascript\nstr('hello').keepLeft(whitespace) // matches \"hello \" but returns \"hello\"\n```\n\u003cbr /\u003e\n\n**.keepRight(other: Parser\u003cU\u003e)** → `Parser\u003cU\u003e`\nRuns `other` parser after, but keeps the result of the second one.\n```javascript\nstr('$').keepRight(number) // matches \"$42\" but returns 42\n```\n\u003cbr /\u003e\n\n#### Utilities\n\n**.slice()** → `Parser\u003cstring\u003e`\nReturns the raw string slice consumed by the parser instead of its structured result.\n```javascript\nmany1(regex(/\\w/)).slice() // returns the matched word as a string\n```\n\u003cbr /\u003e\n\n**.debug(label: string)** → `Parser\u003cT\u003e`\nAdds console logging to a parser's execution for debugging, without changing its behavior.\n```javascript\nnumber.debug('parsing number') // logs debug info when parsing\n```\n\u003cbr /\u003e\n\n### Error Handling \u0026 Advanced Control\n\n**lexeme(parser: Parser\u003cT\u003e)** → `Parser\u003cT\u003e`\nWraps a parser to also consume and discard any trailing whitespace. The key to writing clean, robust grammars.\n```javascript\nconst token = (p) =\u003e lexeme(p) // helper for whitespace-aware parsing\n```\n\u003cbr /\u003e\n\n**label(parser: Parser\u003cT\u003e, msg: string)** → `Parser\u003cT\u003e`\nReplaces a parser's default error message with a more descriptive `msg`.\n```javascript\nlabel(number, 'expected a number') // custom error message\n```\n\u003cbr /\u003e\n\n**context(parser: Parser\u003cT\u003e, msg: string)** → `Parser\u003cT\u003e`\nAdds context to an error message, showing *what* the parser was trying to do when it failed.\n```javascript\ncontext(functionCall, 'in a function call') // adds context to errors\n```\n\u003cbr /\u003e\n\n**lookahead(parser: Parser\u003cT\u003e)** → `Parser\u003cT\u003e`\nSucceeds if `parser` would match, but consumes no input. A \"positive lookahead\".\n```javascript\nlookahead(str('if')).keepRight(keyword) // checks for 'if' without consuming\n```\n\u003cbr /\u003e\n\n**notFollowedBy(parser: Parser\u003cT\u003e)** → `Parser\u003cnull\u003e`\nSucceeds if `parser` would *fail* to match. Consumes no input. A \"negative lookahead\", great for resolving ambiguity.\n```javascript\nstr('if').keepLeft(notFollowedBy(regex(/\\w/))) // 'if' not followed by word char\n```\n\u003cbr /\u003e\n\n**memo(parser: Parser\u003cT\u003e)** → `Parser\u003cT\u003e`\nMemoizes a parser's result at each position, dramatically improving performance for grammars with lots of backtracking.\n\n**leftRecursive(fn: () =\u003e Parser\u003cT\u003e)** → `Parser\u003cT\u003e`\nCorrectly **handles** left-recursive grammars (e.g., `expr = expr + term`), which would cause infinite loops in simple parsers.\n\u003cbr /\u003e\n\n## Generator-Based Parsing (`@doeixd/combi-parse/generator`)\n\nFor writing parsers with a more readable, imperative `async/await`-like style.\n\n**genParser(fn: GeneratorFunction)** → `Parser\u003cT\u003e`\nCreates a parser from a generator function. Inside, `yield` a parser to run it and get its result. `return` the final value.\n```javascript\nconst parser = genParser(function* () {\n  yield str('(')\n  const num = yield number\n  yield str(')')\n  return num\n})\n```\n\u003cbr /\u003e\n\n**`asyncGenParser(fn: AsyncGeneratorFunction)`** → `AsyncParser\u003cT\u003e`\nCreates a parser from an `async function*`. Allows you to `await` promises (e.g., DB calls, API requests) inside your parsing logic.\n\n**gen** → `object`\nA helper object with control-flow utilities for use inside `genParser`, like `gen.tryParsers(...)` and `gen.while(...)`.\n\n\u003cbr /\u003e\n\n## 🤝 Contributing\n\nWe welcome contributions! Whether it's reporting a bug, improving documentation, or submitting a pull request, we'd love to have your help. Please see our contributing guidelines for more details.\n\n\u003cbr /\u003e\n\n## 📄 License\n\nMIT License - see the [LICENSE](LICENSE) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoeixd%2Fcombi-parse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoeixd%2Fcombi-parse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoeixd%2Fcombi-parse/lists"}