{"id":28509982,"url":"https://github.com/rflechner/easyparsing","last_synced_at":"2025-07-03T01:31:02.061Z","repository":{"id":267587864,"uuid":"794613565","full_name":"rflechner/EasyParsing","owner":"rflechner","description":"C# parser combinator helping to create parsers easily","archived":false,"fork":false,"pushed_at":"2024-12-18T20:16:10.000Z","size":334,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-18T21:20:06.797Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","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/rflechner.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}},"created_at":"2024-05-01T15:31:10.000Z","updated_at":"2024-12-16T15:05:27.000Z","dependencies_parsed_at":"2024-12-11T08:36:06.988Z","dependency_job_id":null,"html_url":"https://github.com/rflechner/EasyParsing","commit_stats":null,"previous_names":["rflechner/easyparsing"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rflechner%2FEasyParsing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rflechner%2FEasyParsing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rflechner%2FEasyParsing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rflechner%2FEasyParsing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rflechner","download_url":"https://codeload.github.com/rflechner/EasyParsing/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rflechner%2FEasyParsing/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":258788491,"owners_count":22758221,"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","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":[],"created_at":"2025-06-08T22:37:49.536Z","updated_at":"2025-07-03T01:31:02.053Z","avatar_url":"https://github.com/rflechner.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![.NET](https://github.com/rflechner/EasyParsing/actions/workflows/dotnet.yml/badge.svg)](https://github.com/rflechner/EasyParsing/actions/workflows/dotnet.yml)\n\n# EasyParsing\n\nC# lite parser combinator helping to create parsers easily.\n\n**Benefits**\n- It's easy to create parsers with combinators\n- Extensible\n- Good performances\n- Lightweight  library\n\n## Installation\n\nAfter adding my feed `https://nuget.pkg.github.com/rflechner/index.json` in your sources \n( if you don't known how to do, please read this [https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry) )\n\nYou can install EasyParsing via NuGet :\n\n```bash\ndotnet add package EasyParsing --source https://nuget.pkg.github.com/rflechner/index.json\n```\n\n(It will be soon on nuget.org)\n\n## Examples\n\nThis project contains 2 examples.\n\n- [A simple JSON parser](src/EasyParsing.Samples.Json/readme.md). \n- [A simple Markdown parser](src/EasyParsing.Samples.Markdown/readme.md). \n\n\n### Quick example\n\nHere is a simple example of creating a parser to recognize integers.\n\n```csharp\nusing EasyParsing;\nusing EasyParsing.Dsl.Linq;\nusing static EasyParsing.Dsl.Parse;\n\nvoid TestParser(IParser\u003cint\u003e parser, string s)\n{\n    switch (parser.Parse(s))\n    {\n        case { Success: true, Context.Remaining.IsEmpty: true } result:\n            Console.WriteLine($\"Parsed number: {result.Result}\");\n            break;\n        case { Success: true, Context.Remaining.IsEmpty: false } result:\n            Console.WriteLine($\"Found number: {result.Result} but nothing is parsed.\");\n            break;\n        case { Success: false } result:\n            Console.WriteLine($\"Parsing failed: {result.FailureMessage}\");\n            break;\n    }\n}\n\nIParser\u003cint\u003e integerParser =\n    from digits in ManySatisfy(char.IsDigit)\n    select int.Parse(digits);\n\nTestParser(integerParser, \"123465\");\nTestParser(integerParser, \"123 abc 465\");\nTestParser(integerParser, \"abc 123465\");\n```\n\nOutput will be:\n\n```\nParsed number: 123465\nFound number: 123 but nothing is parsed.\nParsing failed: nothing matched\n```\n\n## Technical details\n\n### Concept of parser combinator\n\nThe main goal is to combine multiples small parsers to create a more complex one.\n\nFor example, if we want to parse decimal `123.456` then we want to parse an `integer`, then a `point`, then as `integer`.\n\nWe create 3 parsers, then we combine all.\n\nIf one parser fails, then the result of combined parser will be a failure.\n\n![schema of pipeline](doc/images/parser-combinator-1.drawio.png \"combinator explained\")\n\n**In C#**\n\nWe define a model in the AST (Abstract Syntax Tree) for JSON decimal value:\n\n```C#\npublic sealed record JsonDecimalValue(decimal Value) : JsonValue;\n```\n\nThe parser for will try to extact decimal value from `\"123.456\"`.\n`123` will be absolute value.\n`.` will be separator.\n`456` will be relative value.\n\n```C#\nIParser\u003cJsonDecimalValue\u003e JsonDecimalValueParser =\n        from abs in ManySatisfy(char.IsDigit)\n        from point in OneCharText('.')\n        from rel in ManySatisfy(char.IsDigit)\n        select new JsonDecimalValue(decimal.Parse($\"{abs}.{rel}\", NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture));\n```\n\n#### More complex grammar parsing\n\nFor example, if we want to parse JSON:\n\n```json\n{\n  \"name\": \"Romain\", \n  \"age\": 39\n}\n```\n\nWe can decompose the problem in multiples steps:\n- detecting objects starts `{` (_followed by potentials spaces_)\n- parse __quoted string__ for properties (_followed by potentials spaces_)\n- parse __value assigment__ character `:` (_followed by potentials spaces_)\n- parse __quoted string__ for values (_followed by potentials spaces_)\n- handle multiple properties assignment separated by `,` (_followed by potentials spaces_)\n- detecting objects ends `}` (_followed by potentials spaces_)\n\nThen we can create a **_parser pipeline_**.\n\nA parser implements interface `IParser\u003cT\u003e` and returns a `IParsingResult\u003cT\u003e`.\n\n`IParsingResult\u003cT\u003e` contains current context and failure or success information.\n\nA pipeline of parsers will use previous `IParsingResult\u003cT\u003e` to known if parsing should continue or if parsing has failed.\n\n\n### Coding\n\nFollowing case, is describing big steps of json parsing like in [JsonParser.cs](src/EasyParsing.Samples.Json/JsonParser.cs). \n\n#### Objects start and end\n\nWe can match only one char `{` and __ignore__ all following spaces.  \nFor this, we can create a small parser.\n\n```C#\nIParser\u003cstring\u003e StartObject = OneCharText('{') \u003e\u003e SkipSpaces();\n```\n\nOperator `\u003e\u003e` will combine both parsers and __ignore__ second result.\n\nFor the end, this is the same job:\n\n```C#\nIParser\u003cstring\u003e EndObject = OneCharText('}') \u003e\u003e SkipSpaces();\n```\n\n#### Quoted strings\n\nQuoted string parsing can be difficult if we handle quotes escaping.\n\n`Parse.CreateStringParser(char quoteChar)` can help to create basic quoted strings parsers.\n\nThen `Parse` static class contains\n\n```C#\npublic static readonly IParser\u003cstring\u003e QuotedTextParser = CreateStringParser('\\'') | CreateStringParser('\"');\n```\n\nThe operator `|` will try to run first parser, if first parser fails then it tries to run second.   \nSo if parser `CreateStringParser('\\'')` fails, we try to parse double-quoted string with `CreateStringParser('\"')`.\n\n#### Between 2 expressions\n\nA useful parser is the `Between` function.\nIt allow to check if an expression is between 2 another described expressions.\n\nFor example, in JSON we want `{` then `properties` then `}`.\n\nThe JSON object parse can be:\n\n```C#\ninternal static readonly IParser\u003cJsonObject\u003e JsonObjectParser = \n    Between(StartObject, PropertiesListParser,  SkipSpaces() \u003e\u003e EndObject)\n        .Select(i =\u003e new JsonObject(i.Item.ToDictionary(p =\u003e p.Name, p =\u003e p.Value)));\n```\n\nAfter creating each value parser, we create a parser which make a choice between each of them.\n\n```C#\ninternal static IParser\u003cJsonValue\u003e ValueParser =\u003e\n    JsonStringValueParser.Cast\u003cJsonStringValue, JsonValue\u003e()\n    | JsonBoolValueParser\n    | JsonDecimalValueParser\n    | JsonLongValueParser\n    | JsonObjectParser\n    | JsonArrayParser;\n```\n\nThen we can hide the logic to the consumer of our JSON parser with a method like:\n\n```C#\npublic static JsonValue ParseJson(string text)\n{\n    var result = ValueParser.Parse(text);\n    \n    if (!result.Success)\n        throw new JsonParsingException(result.FailureMessage);\n    \n    if (result.Result == null)\n        throw new JsonParsingException(\"Could not parse JSON.\");\n    \n    return result.Result;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frflechner%2Feasyparsing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frflechner%2Feasyparsing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frflechner%2Feasyparsing/lists"}