{"id":37031962,"url":"https://github.com/promicsw/flow-expressions","last_synced_at":"2026-01-14T03:56:43.318Z","repository":{"id":200367301,"uuid":"588574559","full_name":"promicsw/flow-expressions","owner":"promicsw","description":"Construct Parsers of any complexity using a declarative fluent syntax in C#.","archived":false,"fork":false,"pushed_at":"2023-12-22T08:26:14.000Z","size":260,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-17T06:16:08.269Z","etag":null,"topics":["compiler","dsl","flow-expressions","flow-logic","fluent-api","parser-builder","parser-generator"],"latest_commit_sha":null,"homepage":"","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/promicsw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-01-13T13:07:22.000Z","updated_at":"2023-11-08T09:51:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"86efacbb-b2c5-4dc8-a784-6c4fe1af0424","html_url":"https://github.com/promicsw/flow-expressions","commit_stats":null,"previous_names":["promicsw/flow-expressions"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/promicsw/flow-expressions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promicsw%2Fflow-expressions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promicsw%2Fflow-expressions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promicsw%2Fflow-expressions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promicsw%2Fflow-expressions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/promicsw","download_url":"https://codeload.github.com/promicsw/flow-expressions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promicsw%2Fflow-expressions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408921,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"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":["compiler","dsl","flow-expressions","flow-logic","fluent-api","parser-builder","parser-generator"],"created_at":"2026-01-14T03:56:42.672Z","updated_at":"2026-01-14T03:56:43.311Z","avatar_url":"https://github.com/promicsw.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flow Expressions \n\n\nConstruct, ready to run,  Parsers of any complexity using a declarative fluent syntax in C#. The system is lightweight, fast, and loosely coupled components provide complete implementation flexibility.\n\n**Note:** This repo uses high performance scanners from the [Scanners](https://github.com/PromicSW/scanners) repo/library.\n\n[![Nuget](https://img.shields.io/nuget/v/Psw.FlowExpressions)](https://www.nuget.org/packages/Psw.FlowExpressions/)\n\u003c!--[![Nuget](https://img.shields.io/nuget/dt/Psw.FlowExpressions)](https://www.nuget.org/packages/Psw.FlowExpression/)--\u003e\n\n## Building Flow Expressions\n\nFlow Expressions are defined by a structure of *FexElements* built via a Fluent API. This defines the logical flow and operations of the expression in a very readable format. \n\nAny logic than can be expressed as a Flow Expression (think flow chart) may be implemented. \nFor starters only **Parsers** will be discussed in this document!\n\n\u003e A Flow Expression operates on a user supplied **Context**, which is any environment that manages and provides input/content/state.\n\u003e\n\u003e For a Parser, the context would be a **Scanner** that manages text scanning and provides \u003ci\u003eTokens\u003c/i\u003e to operate on.\u003cbr/\u003e \n\u003e\n\u003e A comprehensive [FexScanner](Docs/FexScannerExt.md) is provided (derived from *ScriptScanner* in the [Scanners](https://github.com/PromicSW/scanners) repo/library) but you can roll your own if required. \n\nThe following example is a complete **Expression Parser**, including evaluation and error reporting:\n\n```csharp\nvoid ExpressionEval(string calc = \"9 - (5.5 + 3) * 6 - 4 / ( 9 - 1 )\") \n{\n    // Expression Grammar:\n    //   expr    =\u003e factor ( ( '-' | '+' ) factor )* ;\n    //   factor  =\u003e unary ( ( '/' | '*' ) unary )* ;\n    //   unary   =\u003e ( '-'  unary ) | primary ;\n    //   primary =\u003e NUMBER | '(' expression ')' ;\n\n    // Number Stack for calculations:\n    Stack\u003cdouble\u003e numStack = new Stack\u003cdouble\u003e();\n\n    Console.WriteLine($\"Calculate: {calc}\");\n\n    var fex = new FlowExpression\u003cFexScanner\u003e();  \n\n    var expr = fex.Seq(s =\u003e s\n        .Ref(\"factor\")\n        .RepOneOf(0, -1, r =\u003e r\n            .Seq(s =\u003e s.Ch('+').Ref(\"factor\").Act(c =\u003e numStack.Push(numStack.Pop() + numStack.Pop())))\n            .Seq(s =\u003e s.Ch('-').Ref(\"factor\").Act(c =\u003e numStack.Push(-numStack.Pop() + numStack.Pop())))\n         ));\n\n    var factor = fex.Seq(s =\u003e s.RefName(\"factor\")\n        .Ref(\"unary\")\n        .RepOneOf(0, -1, r =\u003e r\n            .Seq(s =\u003e s.Ch('*').Ref(\"unary\").Act(c =\u003e numStack.Push(numStack.Pop() * numStack.Pop())))\n            .Seq(s =\u003e s.Ch('/').Ref(\"unary\")\n                       .Op(c =\u003e numStack.Peek() != 0).OnFail(\"Division by 0\") // Trap division by 0\n                       .Act(c =\u003e numStack.Push(1 / numStack.Pop() * numStack.Pop())))\n         ));\n\n    var unary = fex.Seq(s =\u003e s.RefName(\"unary\")\n        .OneOf(o =\u003e o\n            .Seq(s =\u003e s.Ch('-').Ref(\"unary\").Act(a =\u003e numStack.Push(-numStack.Pop())))\n            .Ref(\"primary\")\n         ));\n\n    var primary = fex.Seq(s =\u003e s.RefName(\"primary\")\n        .OneOf(o =\u003e o\n            .Seq(e =\u003e e.Ch('(').Fex(expr).Ch(')').OnFail(\") expected\"))\n            .Seq(s =\u003e s.NumDecimal(n =\u003e numStack.Push(n)))\n         ).OnFail(\"Primary expected\"));\n\n    var exprEval = fex.Seq(s =\u003e s.GlobalPreOp(c =\u003e c.SkipSp()).Fex(expr).IsEos().OnFail(\"invalid expression\"));\n\n    var scn = new FexScanner(calc);\n\n    Console.WriteLine(exprEval.Run(scn) \n        ? $\"Answer = {numStack.Pop():F4}\" \n        : scn.ErrorLog.AsConsoleError(\"Expression Error:\"));\n}\n```\n\nExample error reporting for the expression parser above:\n\n```dos\nExpression Error:\n9 - ( 5.5 ++ 3 ) * 6 - 4 / ( 9 - 1 )\n-----------^ (Ln:1 Ch:12)\nParse error: Primary expected\n```\n\n## Quick Start\n\n[![Nuget](https://img.shields.io/nuget/v/Psw.FlowExpressions)](https://www.nuget.org/packages/Psw.FlowExpression/)\n\nBelow is a basic console application that defines and runs a Flow Expression to parse a fictitious telephone number:\n```csharp\nusing Psw.FlowExpressions;\n\nQuickStart();\n\nvoid QuickStart() \n{\n    /* Parse demo telephone number of the form:\n     *   (dialing_code) area_code-number: E.g (011) 734-9571\n     *   \n     *   Grammar: '(' (digit)3+ ')' space* (digit)3 (space | '-') (digit)4\n     *   - dialing code: 3 or more digits enclosed in (..)\n     *   - Followed by optional spaces\n     *   - area_code: 3 digits\n     *   - Single space or -\n     *   - number: 4 digits\n     */\n\n    var fex = new FlowExpression\u003cFexScanner\u003e();  // Flow Expression using FexScanner.\n    string dcode = \"\", acode = \"\", number = \"\";  // Will record values in here.\n\n    // Build the flow expression with 'Axiom' tnumber:\n    var tnumber = fex.Seq(s =\u003e s\n        .Ch('(').OnFail(\"( expected\")\n        .Rep(3, -1, r =\u003e r.Digit(v =\u003e dcode += v)).OnFail(\"3+ digit dialing code expected\")\n        .Ch(')').OnFail(\") expected\")\n        .Sp()\n        .Rep(3, r =\u003e r.Digit(v =\u003e acode += v)).OnFail(\"3 digit area code expected\")\n        .AnyCh(\"- \").OnFail(\"- or space expected\")\n        .Rep(4, r =\u003e r.Digit(v =\u003e number += v)).OnFail(\"4 digit number expected\")\n    );\n\n    var testNumber = \"(011) 734-9571\";\n    var scn = new FexScanner(testNumber);  // Scanner instance\n\n    // Run the Axiom and output results:\n    Console.WriteLine(tnumber.Run(scn)\n        ? $\"TNumber OK: ({dcode}) {acode}-{number}\"       // Passed: Display result\n        : scn.ErrorLog.AsConsoleError(\"TNumber Error:\")); // Failed: Display formatted error\n\n}\n```\n\n## Reference Documents\n- [Overview](Docs/FexOverview.md): Introductory overview explaining how Flow Expression work.\n- [Fex Element reference](Docs/FexElementsRef.md): Complete reference of all the Fex Elements (building blocks).\n- [FexScanner](Docs/FexScannerExt.md): The supplied Fex scanner and context extensions reference.\n- [Custom Scanner tutorial](Docs/CustomScanner.md): Describes how to build a custom scanner with examples.\n- Also see the *FexSampleSet* project for all the example code and more.\n\n## About\n\nFlow Expressions provide a novel mechanism of constructing parsers using a simple fluent syntax. \nHaving developed several programming languages and parsers in the past I came up with this idea as an experiment. \nIt think it turned out very well, and find it far more functional and intuitive than say *Parser Combinators* and *PEG*, give it a try!\n\n\u003e It actually makes parser construction quite fun as you can achieve a lot with a few lines of code, while the system does all the heavy lifting.\n\u003e\n\u003e Maybe some of you could help with suggestions, a few nice tutorials and examples etc.\n\nFlow Expressions have since been used to create sophisticated parsers for various projects:\n- ElementScript (coming soon and it's very cool!)\n- Markdown to Html parser\n- CShapCodeDoc - extract and generate code reference documentation (some of the reference material here was generated this way)\n- Several others...\n\n\u003e A potential future extension is to provide a *lookahead* mechanism - will see if there is any interest.\n## Versatility of Flow Expressions\nFlow Expressions are not limited to parsing and can be used to implement other kinds of *flow logic*. \n\nFor example the FexSampleSet project contains several examples including a REPL console menu to run them.\n```con\nRun Sample:\n  1 - Quick Start\n  2 - Use Simple Scanner\n  3 - Expression Eval\n  4 - Expression REPL\n  Blank to Exit\n\u003e\n```\n\nThe REPL menu via conventional coding below:\n\n```csharp\nvoid RunSamples() \n{\n    var samples = new List\u003cSample\u003e {\n        new Sample(\"Quick Start\", () =\u003e QuickStart()),\n        new Sample(\"Use Simple Scanner\", () =\u003e DemoSimpleScanner()),\n        new Sample(\"Expression Eval\", () =\u003e ExpressionEval()),\n        new Sample(\"Expression REPL\", () =\u003e ExpressionREPL()),\n    };\n\n    while (true) {\n        Console.WriteLine(\"Run Sample:\");\n        int i = 1;\n        foreach (Sample sample in samples) {\n            Console.WriteLine($\"  {i++} - {sample.Name}\");\n        }\n        Console.WriteLine(\"  Blank to Exit\");\n        Console.Write(\"\u003e \");\n\n        var val = Console.ReadLine();\n        if (string.IsNullOrEmpty(val)) break;\n\n        Console.Clear();\n\n        if (int.TryParse(val, out i)) {\n            if (i \u003e 0 \u0026\u0026 i \u003c= samples.Count) {\n                Console.WriteLine($\"Run: {samples[i - 1].Name}\");\n                samples[i - 1].Run();\n\n                Console.WriteLine();\n            }\n        }\n    }\n}\n```\n\nAn equivalent REPL menu, via a Flow Expression, shows what's possible: \n\n```csharp\nvoid RunSamplesFex() \n{\n    var samples = new List\u003cSample\u003e {\n        new Sample(\"Quick Start\", () =\u003e QuickStart()),\n        new Sample(\"Use Simple Scanner\", () =\u003e DemoSimpleScanner()),\n        new Sample(\"Expression Eval\", () =\u003e ExpressionEval()),\n        new Sample(\"Expression REPL\", () =\u003e ExpressionREPL()),\n    };\n\n    string val = \"\";\n\n    // FexNoContext is just an empty class since we don't do any scanning here.\n    new FlowExpression\u003cFexNoContext\u003e()\n        .Rep0N(r =\u003e r\n            .Act(c =\u003e Console.WriteLine(\"Run Sample:\"))\n            .RepAct(samples.Count, (c, i) =\u003e Console.WriteLine($\"  {i + 1} - {samples[i].Name}\"))\n            .Act(c =\u003e Console.Write(\"  Blank to Exit\\r\\n\u003e \"))\n            .Op(o =\u003e !string.IsNullOrEmpty(val = Console.ReadLine()))\n            .Act(c =\u003e {\n                Console.Clear();\n                if (int.TryParse(val, out int m)) {\n                    if (m \u003e 0 \u0026\u0026 m \u003c= samples.Count) {\n                        Console.WriteLine($\"Run: {samples[m - 1].Name}\");\n                        samples[m - 1].Run();\n                        Console.WriteLine();\n                    }\n                }\n            })\n        ).Run(new FexNoContext());\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpromicsw%2Fflow-expressions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpromicsw%2Fflow-expressions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpromicsw%2Fflow-expressions/lists"}