{"id":19848658,"url":"https://github.com/polterguy/magic.node","last_synced_at":"2025-05-01T22:30:33.554Z","repository":{"id":44558945,"uuid":"210877672","full_name":"polterguy/magic.node","owner":"polterguy","description":"A generic graph object for the CLR","archived":false,"fork":false,"pushed_at":"2024-01-22T09:21:21.000Z","size":491,"stargazers_count":5,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-23T13:43:18.937Z","etag":null,"topics":["dotnet","dotnet-core","dotnet-standard","graph","magic"],"latest_commit_sha":null,"homepage":"https://ainiro.io","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/polterguy.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":"2019-09-25T15:22:10.000Z","updated_at":"2024-06-19T09:16:49.197Z","dependencies_parsed_at":"2023-02-15T16:50:32.043Z","dependency_job_id":"b9c89501-6ada-4e45-a019-339fa8c81fd3","html_url":"https://github.com/polterguy/magic.node","commit_stats":{"total_commits":413,"total_committers":3,"mean_commits":"137.66666666666666","dds":0.09443099273607747,"last_synced_commit":"95792991b6a2c1d7e28e76707845182909432a54"},"previous_names":[],"tags_count":74,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polterguy","download_url":"https://codeload.github.com/polterguy/magic.node/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251954677,"owners_count":21670849,"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":["dotnet","dotnet-core","dotnet-standard","graph","magic"],"created_at":"2024-11-12T13:17:55.138Z","updated_at":"2025-05-01T22:30:33.138Z","avatar_url":"https://github.com/polterguy.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# magic.node - Nodes, the basic structure in Hyperlambda\n\nmagic.node is a simple name/value/children graph object, in addition to a _\"Hyperlambda\"_ parser, allowing you to\ncreate a textual string representations of graph objects easily transformed to its relational graph object syntax\nand vice versa. This allows you to declaratively create _\"execution trees\"_ using a format similar to YAML, for \nthen to access each individual node, its value, name and children from your C#, CLR code, or Hyperlambda.\n\nHyperlambda is perfect for creating a highly humanly readable relational configuration format, or smaller\nDSL engines, especially when combined with magic.signals and magic.lambda. Below is a small\nexample of Hyperlambda to give you an idea of how it looks like.\n\n```\nfoo:bar\nfoo:int:5\nfoo\n   child1:its-value\n   child2:its-value\n```\n\n3 spaces (SP) opens up the children collection of a node, and allows you to create children associated with\nsome other node. In the example above, the **[child1]** and the **[child2]** nodes, have **[foo]** as their\nparent. A colon `:` separates the name and the value of the node - The name is to the left of the colon, and\nthe value to the right.\nYou can optionally supply a type between a node's name and its value, which you can see above where we add\nthe `:int:` parts between one of our **[foo]** nodes' name and value. If you don't explicitly declare a type\n`string` will be assumed.\n\n## Traversing Hyperlambda from C#\n\nTo traverse the nodes later in for instance C#, you could do something such as the following.\n\n```csharp\nvar root = var result = HyperlambdaParser.Parse(hyperlambda);\n\nforeach (var idxChild in root.Children)\n{\n   var name = idxChild.Name;\n   var value = idxChild.Value;\n   /* ... etc ... */\n}\n```\n\n**Notice** - When you parse Hyperlambda a _\"root node\"_ is explicitly created wrapping all your nodes, and\nthis is the node that's returned to you after parsing. All nodes you declare in your Hyperlambda will be\nreturned to you as children of this root node.\n\n## Hyperlambda types\n\nAlthough the node structure itself can hold any value type you need inside of its `Value` property,\nHyperlambda only supports serialising the following types by default.\n\n* `string` = System.String\n* `short` = System.Int16\n* `ushort` = System.UInt16\n* `int` = System.Int32\n* `uint` = System.UInt32\n* `long` = System.Int64\n* `ulong` = System.UInt64\n* `decimal` = System.Decimal\n* `double` = System.Double\n* `single` = System.Float\n* `float` = System.Float - Alias for above\n* `bool` = System.Boolean\n* `date` = System.DateTime - _Always_ interpreted and serialized as UTC time!\n* `time` = System.TimeSpan\n* `guid` = System.Guid\n* `char` = System.Char\n* `byte` = System.Byte\n* `sbyte` = System.SByte\n* `x` = magic.node.extensions.Expression\n* `node` = magic.node.Node\n\nThe type declaration should be declared in your Hyperlambda in between the name and its value, separated by colon (:).\nThe default type if ommitted is `string` and strings do not need quotes or double quotes for this reason. An example\nof declaring a couple of types associated with a node's value can be found below.\n\n```\n.foo1:int:5\n.foo2:bool:true\n.foo3:string:foo\n.foo4:bar\n```\n\n### Extending the Hyperlambda type system\n\nThe type system is extendible, and you can easily create support for serializing your own types, by using\nthe `Converter.AddConverter` method, that can be found in the `magic.node.extensions` namespace.\nBelow is an example of how to extend the typing system, to allow for serializing and de-serializing instances\nof a `Foo` class into Hyperlambda.\n\n```csharp\n/*\n * Class you want to serialize into Hyperlambda.\n */\nclass Foo\n{\n    public int Value1 { get; set; }\n    public decimal Value2 { get; set; }\n}\n\n/*\n * Adding our converter functions, and associating them\n * with a type, and a Hyperlambda type name.\n */\nConverter.AddConverter(\n    typeof(Foo),\n    \"foo\",\n    (obj) =\u003e {\n        var foo = obj as Foo;\n        return (\"foo\", $\"{foo.Value1}-{foo.Value2}\");\n    }, (obj) =\u003e {\n        var str = (obj as string).Split('-');\n        return new Foo\n        {\n            Value1 = int.Parse(str[0]),\n            Value2 = decimal.Parse(str[1]),\n        };\n    });\n```\n\nThe above will allow you to serialize instances of `Foo` into your Hyperlambda, and\nde-serialize these instances once needed. An example of adding a `Foo` instance into\nyour Hyperlambda can be found below.\n\n```\n.foo:foo:5-7\n```\n\nLater you can retrieve your `Foo` instances in your slots, using something\nresembling the following, and all parsing and conversion will be automatically\ntaken care of.\n\n```\nvar foo = node.Get\u003cFoo\u003e();\n```\n\n## String literals in Hyperlambda\n\nHyperlambda also support strings the same way C# supports strings, using any of the following string representations.\n\n```\n// Single quotes\n.foo:'howdy world this is a string'\n\n// Double quotes\n.foo:\"Howdy world, another string\"\n\n// Multiline strings\n.foo:@\"Notice how the new line doesn't end the string\n    here!\"\n```\n\nEscape characters are supported for both single quote and double quote strings the same way they\nare supported in C#, allowing you to use e.g. `\\r\\n` etc.\n\n## Lambda expressions\n\nLambda expressions are kind of like XPath expressions, except they will references nodes\nin your Node graph object instead of XML nodes. Below is an example to give you an idea.\n\n```\n.foo:hello world\nget-value:x:@.foo\n\n// After invocation of the above **[get-value]**, its value will be \"hello world\".\n```\n\nMost slots in Magic can accept expressions to reference nodes, values of nodes, and children of\nnodes somehow. This allows you to modify the lambda graph object, as it is currently being executed,\nand hence allows you to modify _\"anything\"_ from _\"anywhere\"_. This resembles XPath expressions\nfrom XML.\n\n### Iterators in lambda expressions\n\nAn expression is constructed from one or more _\"iterator\"_. This makes an expression\nbecome _\"dynamically chained Linq statements\"_, where each iterator reacts upon\nthe results of its previous iterator. Each iterator takes as input an `IEnumerable`,\nand returns as its result another `IEnumerable`, where the content of the iterator\nsomehow changes its given input, according to whatever the particular iterator's\nimplementation does. This approach just so happens to be perfect for retrieving\nsub-sections of graph objects.\n\nEach iterator ends with a _\"/\"_ or a CR/LF\nsequence, and before its end, its value defines what it does. For instance the above iterator in\nthe __[get-value]__ invocation, starts out with a _\"@\"_. This implies that the iterator will find the\nfirst node having a name of whatever follows its _\"@\"_. For the above this implies looking for the first\nnode who's name is _\".foo\"_. To see a slightly more advanced example, imagine the following.\n\n```\n.data\n   item1:john\n   item2:thomas\n   item3:peter\nget-value:x:@.data/*/item2\n```\n\nIt might help to transform the above expression into humanly readable language. Its\nEnglish equivalent hence becomes as follows.\n\n\u003e Find the node with the name of '.data', then retrieve its children, and filter away everything not having a name of 'item2'\n\nOf course, the result of the above becomes _\"thomas\"_.\n\nBelow is a list of all iterators that exists in magic. Substitute _\"xxx\"_ with a string,\n_\"n\"_ with a number, and _\"x\"_ with an expression.\n\n* `*` Retrieves all children of its previous result\n* `#` Retrieves the value of its previous result as a node by reference\n* `-` Retrieves its previous result set's _\"younger sibling\"_ (previous node)\n* `+` Retrieves its previous result set's _\"elder sibling\"_ (next node)\n* `.` Retrieves its previous result set's parent node(s)\n* `^xxx` Retrieves the first ancestor node with the specified _\"xxx\"_ name. Similar to `@` iterator but does not traverse siblings, only direct ancestors up in hierarchy\n* `..` Retrieves the root node\n* `**` Retrieves its previous result set's descendant, with a _\"breadth first\"_ algorithm\n* `--` Retrieves all ancestors and older siblings upwards in object\n* `!xxx` Traverses all descendants except those matching specified _\"xxx\"_ name and returns\n* `{x}` Extrapolated expression that will be evaluated assuming it yields one result, replacing itself with the value of whatever node it points to\n* `=xxx` Retrieves the node with the _\"xxx\"_ value, converting to string if necessary\n* `[n,n]` Retrieves a subset of its previous result set, implying _\"from, to\"_ meaning \\[n1,n2\\\u003e\n* `@xxx` Returns the first node _\"before\"_ in its hierarchy that matches the given _\"xxx\"_ in its name\n* `n` (any number) Returns the n'th child of its previous result set\n* `[x|y]` Pipe separated list of names returning all nodes having a name of either _\"x\"_ or _\"y\"_\n\nNotice, you can escape iterators by using backslash \"\\\\\". This allows you to look for nodes who's names\nare for instance _\"3\"_, without using the n'th child iterator, which would defeat the purpose. In addition,\nyou can quote iterators by using double quotes `\"`, to allow for having iterators with values that are normally\nnot legal within an iterator, such as `/`, etc. If you quote an iterator you have to quote the entire expression.\nBelow is an example of a slightly more advanced expression.\n\n```\n.foo\n   howdy:wo/rld\n   jo:nothing\n   howdy:earth\n.dyn:.foo\nfor-each:x:@\"./*/{@.dyn}/*/\"\"=wo/rld\"\"\"\n   set-value:x:@.dp/#\n      :thomas was here\n```\n\nAfter evaluating the above Hyperlambda, the value of all nodes having _\"wo/rld\"_ as their value\ninside of **[.foo]** will be updated to become _\"thomas was here\"_. Obviously, the above expression\nis a ridiculous complex example, that you will probably never encounter in your own code. However,\nfor reference purposes, let's break it down into its individual parts.\n\n1. Get parent node\n2. Get all children\n3. Filter away everything not having the name of the value of `{@.dyn}`, which resolves to the equivalent of `:x:@.dyn`, being an expression, who's result becomes _\".foo\"_.\n4. Get its children\n5. Find all nodes who's value is _\"wo/rld\"_.\n\n98% of your expressions will have 1-3 iterators, no complex escaping, and no parameters.\nAnd in fact, there are thousands of lines of Hyperlambda code in Magic's middleware, and\n98% of these expressions are as simple as follows.\n\n```\n.arguments\n   foo1:string\nget-value:x:@.arguments/*/foo1\n```\n\nWhich translates into the following English.\n\n\u003e Give me the value of any **[foo1]** nodes, inside of the first **[.arguments]** node you can find upwards in the hierarchy.\n\nExpressions can also be extrapolated, which allows you to parametrise your expressions, by nesting\nexpressions, substituting parts of your expression dynamically as your code is executed. Imagine\nthe following example.\n\n```\n.arg1:foo2\n.data\n   foo1:john\n   foo2:thomas\n   foo3:peter\nget-value:x:@.data/*/{@.arg1}\n```\n\nThe above expression will first evaluate the `{@.arg1}` parts, which results in _\"foo2\"_, then evaluate the\nouter expression, which now will look like this `@.data/*/foo2`. You can also extrapolate expressions on\nvalues, such as illustrated below.\n\n```\n.arg1:thomas\n.data\n   foo1:john\n   foo2:thomas\n   foo3:peter\nget-name:x:@.data/*/={@.arg1}\n```\n\n### Extending lambda expressions/iterators\n\nYou can easily extend expressions in Magic, either with a _\"static\"_ iterator, implying\na direct match - Or with a dynamic parametrized iterator, allowing you to create iterators that\nrequires _\"parameters\"_. To extend the supported iterators, use any of the following two static\nmethods.\n\n* `Iterator.AddStaticIterator` - Creates a _\"static\"_ iterator, implying a direct match.\n* `Iterator.AddDynamicIterator` - Creates a _\"dynamic iterator create function\"_.\n\nBelow is a C# example, that creates a dynamic iterator, that will only return nodes having a value,\nthat once converted into a string, has _exactly_ `n` characters, not less and not more.\n\n```csharp\nIterator.AddDynamicIterator('%', (iteratorValue) =\u003e {\n    var no = int.Parse(iteratorValue.Substring(1));\n    return (identity, input) =\u003e {\n        return input.Where(x =\u003e x.Get\u003cstring\u003e()?.Length == no);\n    };\n});\n\nvar hl = @\"foo\n   howdy1:XXXXX\n   howdy2:XXX\n   howdy3:XXXXX\n\";\nvar lambda = HyperlambdaParser.Parse(hl);\n\nvar x = new Expression(\"../**/%3\");\nvar result = x.Evaluate(lambda);\n```\n\nNotice how the iterator we created above, uses the `%3` parts of the expression, to parametrize\nitself. If you exchange 3 with 5, it will only return **[howdy1]** and **[howdy3]** instead,\nsince it will look for values with 5 characters instead. The `Iterator` class can be found\nin the `magic.node.extensions` namespace.\nYou can use the above syntax to override the default implementation of iterators, although\nI wouldn't recommend it, since it would create confusion for others using your modified version.\n\n**Notice** - To create an extension iterator is an exercise you will rarely if _ever_ need to do,\nbut is included here for reference purposes.\n\n### Parsing Hyperlambda from C#\n\nMagic allows you to easily parse Hyperlambda from C# if you need it, which can be done as follows.\n\n```csharp\nusing magic.node.extensions.hyperlambda;\n\nvar hl = GetHyperlambdaAsString();\nvar lambda = HyperlambdaParser.Parse(hl);\n```\n\nThe `GetHyperlambdaAsString` above could for instance load Hyperlambda from a file, retrieve it\nfrom your network, or some other way retrieve a snippet of Hyperlambda text. The `HyperlambdaParser.Parse`\nparts above will return your Hyperlambda as its `Node` equivalent. The `Parser` class also have an\noverloaded constructor for taking a `Stream` instead of a `string`.\n\n**Notice** - The `Node` returned above will be a root node, wrapping all nodes found in your\nHyperlambda as children nodes. This is necessary in order to avoid enforcing a single _\"document node\"_\nthe way XML does.\nOnce you have a `Node` object, you can easily reverse the process by using the `HyperlambdaGenerator`\nclass, and its `GetHyperlambda` method such as the following illustrates.\n\n```csharp\nusing magic.node.extensions.hyperlambda;\n\nvar hl1 = GetHyperlambdaAsString();\nvar result = HyperlambdaParser.Parse(hl1);\nvar hl2 = HyperlambdaGenerator.GetHyperlambda(result.Children);\n```\n\n## Documenting nodes, arguments to slots, etc\n\nWhen referencing nodes in the documentation for Magic, it is common to reference them like __[this]__, where\n_\"this\"_ would be the name of some node - Implying in __bold__ characters, wrapped by square [brackets].\n\n## Magic's GitHub project page\n\nMagic is 100% Open Source and you can find the primary project GitHub page [here](https://github.com/polterguy/magic).\n\n## Project website for magic.node\n\nThe source code for this repository can be found at [github.com/polterguy/magic.node](https://github.com/polterguy/magic.node), and you can provide feedback, provide bug reports, etc at the same place.\n\n- ![Build status](https://github.com/polterguy/magic.node/actions/workflows/build.yaml/badge.svg)\n- [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=ncloc)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n- [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.node\u0026metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=polterguy_magic.node)\n\n## Copyright and maintenance\n\nThe projects is copyright Thomas Hansen 2023 - 2024, and professionally maintained by [AINIRO.IO](https://ainiro.io).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolterguy%2Fmagic.node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolterguy%2Fmagic.node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolterguy%2Fmagic.node/lists"}