{"id":17008723,"url":"https://github.com/i-e-b/gool","last_synced_at":"2025-10-16T19:42:44.777Z","repository":{"id":1699323,"uuid":"2427893","full_name":"i-e-b/Gool","owner":"i-e-b","description":"A fast, robust, and thread-safe parser-combinator library for C#, with a fluent BNF-like interface","archived":false,"fork":false,"pushed_at":"2025-09-24T12:50:53.000Z","size":2464,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-10T08:18:50.885Z","etag":null,"topics":["csharp","lexer","lexer-generator","parser","parser-combinators"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/Gool/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/i-e-b.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2011-09-21T06:02:52.000Z","updated_at":"2025-09-24T12:50:57.000Z","dependencies_parsed_at":"2024-11-28T22:40:58.791Z","dependency_job_id":"b13215f9-f5da-4076-888a-110b7a5c972d","html_url":"https://github.com/i-e-b/Gool","commit_stats":null,"previous_names":["i-e-b/gool"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/i-e-b/Gool","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i-e-b%2FGool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i-e-b%2FGool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i-e-b%2FGool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i-e-b%2FGool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/i-e-b","download_url":"https://codeload.github.com/i-e-b/Gool/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i-e-b%2FGool/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279230843,"owners_count":26130684,"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-16T02:00:06.019Z","response_time":53,"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":["csharp","lexer","lexer-generator","parser","parser-combinators"],"created_at":"2024-10-14T05:29:06.556Z","updated_at":"2025-10-16T19:42:44.730Z","avatar_url":"https://github.com/i-e-b.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"Gool is a lexer/parser for C#\n==================================\n\nA fast, robust, and thread-safe parser-combinator library for C#, with a fluent BNF-like interface for building parsers.\n\nUse this to read and interpret a wide range of text-based input -- including file formats, data structures, and \nprogramming languages.\n\nBy being a run-time library inside the main program, grammars can be built and modified as required,\nwith even complex structures taking microseconds to build.\n\n### Unique features\n\n- Contextual parsing: can build new patterns at run-time *and* at parse time\n- Patterns can be computed using any C# code\n- Easily expanded to handle complex patterns\n- Use all your existing navigation and refactoring tools\n\n### When should I use this?\n\nIf you have a complex and/or fragile set of regular expressions, try using a parser instead.\n\nSee [Sample Parsers](https://github.com/i-e-b/Gool/tree/master/SamplesStd) for fully functional examples.\n\nBasic example\n-------------\n\nDefining the parser: \n\n```csharp\nBNF // Basic infix arithmetic expressions\n    number     = FractionalDecimal(),                 // Built-in helper for signed numbers\n\n    factor     = number |  ('(' \u003e _expression \u003e ')'), // Number or parenthesised expression\n    power      = factor \u003e !('^' \u003e factor),            // Factor, with optional '^' + exponent\n    term       = power  %  ('*' | '/'),               // Powers, optionally joined with '*' or '/'\n    expression = term   %  ('+' | '-');               // Terms, optionally joined will '+' or '-'\n```\n\nReading an input:\n\n```csharp\nvar result = expression.ParseEntireString( // Run the parser, refuse partial matches\n    \"(6.5 + 3) * (5.5 - -0.2e1)\"\n    );\n\nvar tree = TreeNode.FromParserMatch(result, true);        // Interpret the raw parse tree\nvar final = TreeNode.TransformTree(tree, ApplyOperation); // Apply functions to reduce tree to value\n\nConsole.WriteLine(final); // 71.25\n```\n\n(some details removed for clarity -- see bottom of this readme for full implementation)\n\nBNF Syntax\n----------\n\n### Terminal parsers:\n\n- `'…'` → *Character* parser that matches a single literal character in the input\n- `\"…\"` → *String* parser that matches a literal string in the input\n- `BNF.Regex(\"…\")` → *Regex* parser that matches a string based on a regex pattern.\n- `BNF.OneOf(…)` → Match a single character from the set provided\n- `BNF.NoneOf(…)` → Match any single character that is **not** in the set provided\n- `BNF.AnyChar` → Parser that matches any single character.\n- `BNF.Empty` → Parser that matches an empty string (useful in unions)\n- `BNF.EndOfInput` → Parser that matches the end of input (parsers will normally accept partial matches)\n- `BNF.LineEnd` → Parser that matches a line end (either `\\r`, or `\\n`, or `\\r\\n`)\n- `BNF.WhiteSpace` → Parser that matches a single character of white-space\n\n### Combining parsers:\n\n- a `|` b → Create a *union* parser that matches the **longest** result from either **a** or **b**. Parser will match if only one of **a** and **b** match, *or* if both **a** and **b** match.\n    - Example: `\"hello\" | \"world\"` matches `hello` or `world` \n    - Example: `\"on\" | \"one\"` matches `on` and `one`. `+( \"on\" | \"one\" )` will match `oneone` as {`one`, `one`}\n- a `\u003e` b → Create a *sequence* parser that matches **a** then **b**\n    - Example: `'x' \u003e 'y'` matches `xy` but not `x` or `y`\n- a `\u003c` b → Create a *terminated list* parser that matches a list of **a**, each being terminated by **b**. The last item **a** must be terminated.\n   - Example: `'x' \u003c ';'` matches `x;x;x;` and `x;`, but not `x` or `x;x`\n- a `%` b → Create a *delimited list* parser that matches a list of **a**, delimited by **b**. A trailing delimiter is not matched.\n    - Example: `'x'%','` matches `x` and `x,x`, but not `x,x,`\n- `-`a → Create an *optional repeat* parser that matches zero or more **a**\n   - Example: `-\"xy\"` matches `xyxy`, `xy`, and *empty*\n- `+`a → Create a *repeat* parser that matches one or more **a**\n   - Example: `+\"xy\"` matches `xy` and `xyxy`, but not *empty*\n- `!`a → Create an *option* parser that matches zero or one **a**\n   - Example: `!\"xy\"` matches `xy` and *empty*, but not `xyxy`\n   - Can also be expressed `BNF.Optional(`a`)`\n- a `\u0026` b → Create an *intersection* parser that matches (**a** then **b**) or (**b** then **a**)\n   - Example: `'x'\u0026'y'` matches `xy` and `yx`, but not `xx` or `yy` \n- a `^` b → Create an *exclusion* parser that matches **a** or **b** but not both\n    - Example: `'x'^'y'` matches `x` and `y`, but not `xy` or `yx`\n- a `/` b → Create a *difference* parser that matches **a** but not **b**\n    - Example: `\"on\" / \"one\"` matches `on` but not `one`. `+( 'x' / '.' )` will match `xx.` as {`x`, `x`}\n- `~`a → Create a *non-consuming* parser that must match **a**, but does not consume the match\n  - Example: `'x' \u003e ~'y' \u003e \"yz\"` matches `xyz` as `x` and `yz`. This is useful for compatibility with PEG grammars.\n- a `\u003e=` b → Creates a *preference-union* (also called *preference-alternative* or *ordered choice*) parser from two sub-parsers. This returns the first successful match from left-to-right\n    - Example: `\"a\" \u003e= \"app\" \u003e= \"apple\"` would only ever match `a`. This is useful for compatibility with PEG grammars.\n\nParsers generated by BNF can be used repeatedly.\n\nMore Details\n------------\n\n### What are Parser-Combinators\n\nParser-Combinators are components that you can build into structures that encode languages (grammars).\nThe building can be done using a human-readable syntax, building parsers of increasing complexity on top of simpler parts.\nDetailed grammars and languages can be processed in an efficient way.\n\nBy structuring the parser-combinator library in a particular way, building parsers is the same as writing a grammar itself.\nTherefore instead of describing how to parse a language, a user must only specify the language itself.\nThe result is a working parser.\n\nThe resulting parsers are equivalent to recursive descent parsers with contextual curtailment.\nThe parsing process can handle left-recursive grammars and ambiguous grammars, although these will\nresult in less efficient parsing.\n\n### Scanners\n\nParsers operate over a 'scanner', which is an input string plus transforms and contextual data.\nFor common cases, you won't need to create one directly -- just use `BNF.ParseString` or `BnfPackage.ParseString`.\n\nScanners handle case-conversion and white-space skipping if you use those options.\n\nBecause scanners hold context for a parse, they cannot be reused or shared between parse attempts.\n\n### Tags, scopes, and trees\n\nThe basic output from a parser is a `ParserMatch`, which gives a complete tree of all matches, including those from combined\nparsers. `ParserMatch` also gives access to the parser that made the match, and the scanner that was used for input.\n\nThe `ParserMatch` tree contains all the information from a result, but often this is too much.\n\nAny parser can be tagged with a string value, and this can be used to extract salient information from the tree.\n\n#### TaggedTokensDepthFirst / TaggedTokensBreadthFirst\n\nYou can request a sequence of `ParserMatch`es from the result tree, only returning tagged results.\nTags are **not** inherited by parent matches.\n\n#### ScopeNode.FromMatch\n\nThe scoped node tree ignores the `ParserMatch` hierarchy, and uses `.OpenScope()` and `.CloseScope()`\nto build a different structure. This is useful for structured data (like JSON and XML) and otherwise scoped\ndata that use open and close markers -- like (`{`,`}`) or (`begin`,`end`) in many programming languages.\n\n#### TreeNode.FromParserMatch\n\nGeneral tree nodes match the `ParserMatch` hierarchy, but only including nodes with a tag or scope set.\n\nThe `Pivot` scope has a specific effect on general trees, 'lifting' them to a level above non-pivot peers.\nThis is useful for chains of operators:\n\nGiven the parser `sum` from:\n\n```csharp\nBNF number = BNF.Regex(\"[0-9]+\").Tag(\"num\");\nBNF addSub = BNF.OneOf('+', '-').Tag(\"op\");\nBNF sum = number % addSub;\n```\n\nand the input:\n\n```csharp\nvar result = sum.ParseEntireString(\n                             \"1+2\",\n                             );\nvar tree = TreeNode.FromParserMatch(result, false);\n```\n\noutputs `tree` as:\n```\n┌───── 1   \n│      +   \n└──  2     \n```\n\nbut changing `addSub` to `BNF.OneOf('+', '-').Tag(\"op\").PivotScope();` results in\n\n```\n  ┌──1  \n +│     \n  └──2  \n```\n\nDetailed examples\n-----------------\n\nSee [Sample Parsers](https://github.com/i-e-b/Gool/tree/master/SamplesStd) for more fully functional examples.\n\n### Basic infix arithmetic calculator\n\n```csharp\nusing Gool;\nusing static Gool.BNF; // Include BNF methods without needing 'BNF.' everywhere\n\npublic double EvaluateExpression(string expression)\n{\n    var result = Arithmetic().ParseEntireString(expression);    // Step 1: parse input\n    var tree = TreeNode.FromParserMatch(result, prune: true);   // Step 2: build expression tree\n    var final = TreeNode.TransformTree(tree, ApplyOperation);   // Step 3: reduce the tree to a value\n    \n    return final;\n}\n\npublic static Package Arithmetic()\n{\n    var _expression = Forward();\n\n    BNF\n        add_sub = OneOf('+', '-'),\n        mul_div = OneOf('*', '/'),\n        exp     = '^';\n\n    BNF\n        number     = FractionalDecimal(),\n        factor     = number | ('(' \u003e _expression \u003e ')'),\n        power      = factor \u003e !(exp \u003e factor),\n        term       = power % mul_div,\n        expression = term % add_sub;\n\n    _expression.Is(expression);\n\n    add_sub.TagWith(Operation).PivotScope();\n    mul_div.TagWith(Operation).PivotScope();\n    exp    .TagWith(Operation).PivotScope();\n    number .TagWith(Value);\n\n    return expression.WithOptions(Options.SkipWhitespace);\n}\n\npublic const string Operation = \"operation\";\npublic const string Value = \"value\";\n\nprivate static TreeNode ApplyOperation(TreeNode node)\n{\n    if (node.Source.Tag is null) return node.Children[0]; // pull child up through joining nodes\n\n    if (node.Source.Tag != Operation) return node; // only look at operation nodes\n    var operation = node.Source.Value;\n\n    if (node.Children.Count \u003c 2) throw new Exception(\"Invalid expression\");\n    var left = node.Children[0].Source;\n    var right = node.Children[1].Source;\n\n    if (!double.TryParse(left.Value, out var a)\n     || !double.TryParse(right.Value, out var b)) return node; // one of our children is not a number\n\n    // Both children are values: perform the operation\n    var result = operation switch\n    {\n        \"+\" =\u003e a + b,\n        \"-\" =\u003e a - b,\n        \"*\" =\u003e a * b,\n        \"/\" =\u003e a / b,\n        \"^\" =\u003e Math.Pow(a, b),\n        _ =\u003e throw new NotImplementedException($\"Operation not implemented: '{operation}'\")\n    };\n\n    // Return a new node with the calculated value\n    return TreeNode.FromString(result.ToString(CultureInfo.InvariantCulture), Value);\n}\n```\n\n### Simplified XML Parser\n\n```csharp\nBNF // Fragments\n    text       = Regex(\"[^\u003c\u003e]+\"),\n    identifier = Regex(\"[_a-zA-Z][_a-zA-Z0-9]*\"),\n    whitespace = Regex(@\"\\s+\");\n\nBNF // Literals\n    quoted_string = '\"' \u003e identifier \u003e '\"',\n    attribute     = whitespace \u003e identifier \u003e '=' \u003e quoted_string;\n\nBNF // tags\n    tag_id    = identifier.Tagged(TagId),\n    open_tag  = '\u003c' \u003e tag_id \u003e -attribute \u003e '\u003e',\n    close_tag = \"\u003c/\" \u003e tag_id \u003e '\u003e';\n\nattribute.TagWith(Attribute);\ntext.TagWith(Text);\nopen_tag.TagWith(OpenTag).OpenScope();\nclose_tag.TagWith(CloseTag).CloseScope();\n\nreturn Recursive(tree =\u003e -(open_tag \u003e -(tree | text) \u003e close_tag)).WithOptions(Options.None);\n```\n\n\n### Full spec JSON parser\n\nFrom https://www.json.org/json-en.html\n\n```csharp\nvar value = Forward();\n\nBNF // Basic components\n    ws     = AnyWhiteSpace,\n    number = FractionalDecimal(groupMark: \"\", decimalMark: \".\", allowLeadingZero: false, allowLeadingPlus: false);\n\nBNF // Strings\n    unicodeEsc    = 'u' \u003e CharacterInRanges(('0', '9'), ('a', 'f'), ('A', 'F')).Repeat(4),\n    escape        = OneOf('\"', '\\\\', '/', 'b', 'f', 'n', 'r', 't') | unicodeEsc,\n    character     = NoneOf('\"', '\\\\') | ('\\\\' \u003e escape),\n    characters    = -character,\n    quoted_string = '\"' \u003e characters \u003e '\"';\n\nBNF // Elements (lone or in arrays)\n    element  = ws \u003e value \u003e ws,\n    elements = element % ',';\n\nBNF // Members of objects\n    member_key = quoted_string.Copy(),\n    member     = ws \u003e member_key \u003e ws \u003e ':' \u003e element,\n    members    = member % ',';\n\nBNF // Objects\n    object_enter = '{',\n    object_leave = '}',\n    object_block = object_enter \u003e (ws | members) \u003e object_leave;\n\nBNF // Arrays\n    array_enter = '[',\n    array_leave = ']',\n    array_block = array_enter \u003e elements \u003e array_leave;\n\nBNF // Single values\n    primitive = quoted_string \u003e= number \u003e= \"true\" \u003e= \"false\" \u003e= \"null\";\n\nvalue.Is(object_block \u003e= array_block \u003e= primitive);\n\narray_enter.OpenScope().TagWith(\"array\");\narray_leave.CloseScope();\n\nobject_enter.OpenScope().TagWith(\"object\");\nobject_leave.CloseScope();\n\nmember_key.TagWith(\"key\");\nprimitive.TagWith(\"value\");\n\nreturn element.WithOptions(Options.None);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-e-b%2Fgool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fi-e-b%2Fgool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-e-b%2Fgool/lists"}