magic.node 14.0.10

There is a newer version of this package available.
See the version list below for details.
dotnet add package magic.node --version 14.0.10                
NuGet\Install-Package magic.node -Version 14.0.10                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="magic.node" Version="14.0.10" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add magic.node --version 14.0.10                
#r "nuget: magic.node, 14.0.10"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install magic.node as a Cake Addin
#addin nuget:?package=magic.node&version=14.0.10

// Install magic.node as a Cake Tool
#tool nuget:?package=magic.node&version=14.0.10                

Node support for Hyperlambda and Magic

magic.node is a simple name/value/children graph object, in addition to a "Hyperlambda" parser, allowing you to create a textual string representations of graph objects easily transformed to its relational graph object syntax and vice versa. This allows you to easily declaratively create execution trees using a format similar to YAML, for then to access each individual node, its value, name and children from your C#, CLR code, or Hyperlambda. For the record, Hyperlambda is much easier to understand than YAML. Hyperlambda is perfect for creating a highly humanly readable relational configuration format, or smaller DSL engines, especially when combined with magic.signals and magic.lambda. Below is a small example of Hyperlambda to give you an idea of how it looks like.

foo:bar
foo:int:5
foo
   child1:its-value
   child2:its-value

3 spaces (SP) opens up the children collection of a node, and allows you to create children associated with some other node. In the example above, the [child1] and the [child2] nodes, have [foo] as their parent. A colon : separates the name and the value of the node - The name is to the left of the colon, and the value to the right. You can optionally supply a type between a node's name and its value, which you can see above where we add the :int: parts between one of our [foo] nodes' name and value. If you don't explicitly declare a type string will be assumed.

Parsing Hyperlambda from C#

To traverse the nodes later in for instance C#, you could do something such as the following.

var root = var result = HyperlambdaParser.Parse(hyperlambda);

foreach (var idxChild in root.Children)
{
   var name = idxChild.Name;
   var value = idxChild.Value;
   /* ... etc ... */
}

Notice - When you parse Hyperlambda a "root node" is explicitly created wrapping all your nodes, and this is the node that's returned to you after parsing. All nodes you declare in your Hyperlambda will be returned to you as children of this root node.

Supported types

Although the node structure itself can hold any value type you need inside of its Value property, Hyperlambda only supports serialising the following types by default.

  • string = System.String
  • short = System.Int16
  • ushort = System.UInt16
  • int = System.Int32
  • uint = System.UInt32
  • long = System.Int64
  • ulong = System.UInt64
  • decimal = System.Decimal
  • double = System.Double
  • single = System.Float
  • float = System.Float - Alias for above
  • bool = System.Boolean
  • date = System.DateTime - Always interpreted and serialized as UTC time!
  • time = System.TimeSpan
  • guid = System.Guid
  • char = System.Char
  • byte = System.Byte
  • x = magic.node.extensions.Expression
  • node = magic.node.Node

The type declaration should be declared in your Hyperlambda in between the name and its value, separated by colon (:). The default type if ommitted is string and strings do not need quotes or double quotes for this reason. An example of declaring a couple of types associated with a node's value can be found below.

.foo1:int:5
.foo2:bool:true
.foo3:string:foo
.foo4:bar

Extending the type system

The type system is extendible, and you can easily create support for serializing your own types, by using the Converter.AddConverter method, that can be found in the magic.node.extensions namespace. Below is an example of how to extend the typing system, to allow for serializing and de-serializing instances of a Foo class into Hyperlambda.

/*
 * Class you want to serialize into Hyperlambda.
 */
class Foo
{
    public int Value1 { get; set; }
    public decimal Value2 { get; set; }
}

/*
 * Adding our converter functions, and associating them
 * with a type, and a Hyperlambda type name.
 */
Converter.AddConverter(
    typeof(Foo),
    "foo",
    (obj) => {
        var foo = obj as Foo;
        return ("foo", $"{foo.Value1}-{foo.Value2}");
    }, (obj) => {
        var str = (obj as string).Split('-');
        return new Foo
        {
            Value1 = int.Parse(str[0]),
            Value2 = decimal.Parse(str[1]),
        };
    });

The above will allow you to serialize instances of Foo into your Hyperlambda, and de-serialize these instances once needed. An example of adding a Foo instance into your Hyperlambda can be found below.

.foo:foo:5-7

Later you can retrieve your Foo instances in your slots, using something resembling the following, and all parsing and conversion will be automatically taken care of.

var foo = node.Get<Foo>();

String literals

Hyperlambda also support strings the same way C# supports string, using any of the following string representations.

// Single quotes
.foo:'howdy world this is a string'

// Double quotes
.foo:"Howdy world, another string"

// Multiline strings
.foo:@"Notice how the new line doesn't end the string
    here!"

Escape characters are supported for both single quote and double quote strings the same way they are supported in C#, allowing you to use e.g. \r\n etc.

Lambda expressions

Lambda expressions are kind of like XPath expressions, except they will references nodes in your Node graph object instead of XML nodes. Below is an example to give you an idea.

.foo:hello world
get-value:x:@.foo

// After invocation of the above **[get-value]**, its value will be "hello world".

Most slots in Magic can accept expressions to reference nodes, values of nodes, and children of nodes somehow. This allows you to modify the lambda graph object, as it is currently being executed, and hence allows you to modify "anything" from "anywhere". This resembles XPath expressions from XML.

Iterators

An expression is constructed from one or more "iterator". This makes an expression become "dynamically chained Linq statements", where each iterator reacts upon the results of its previous iterator. Each iterator takes as input an IEnumerable, and returns as its result another IEnumerable, where the content of the iterator somehow changes its given input, according to whatever the particular iterator's implementation does. This approach just so happens to be perfect for retrieving sub-sections of graph objects.

Each iterator ends with a "/" or a CR/LF sequence, and before its end, its value defines what it does. For instance the above iterator in the [get-value] invocation, starts out with a "@". This implies that the iterator will find the first node having a name of whatever follows its "@". For the above this implies looking for the first node who's name is ".foo". To see a slightly more advanced example, imagine the following.

.data
   item1:john
   item2:thomas
   item3:peter

get-value:x:@.data/*/item2

It might help to transform the above expression into humanly readable language. Its English equivalent hence becomes as follows.

Find the node with the name of '.data', then retrieve its children, and filter away everything not having a name of 'item2'

Of course, the result of the above becomes "thomas".

Below is a list of all iterators that exists in magic. Substitute "xxx" with a string, "n" with a number, and "x" with an expression.

  • * Retrieves all children of its previous result
  • # Retrieves the value of its previous result as a node by reference
  • - Retrieves its previous result set's "younger sibling" (previous node)
  • + Retrieves its previous result set's "elder sibling" (next node)
  • . Retrieves its previous reult set's parent node(s)
  • .. Retrieves the root node
  • ** Retrieves its previous result set's descendant, with a "breadth first" algorithm
  • {x} Extrapolated expression that will be evaluated assuming it yields one result, replacing itself with the value of whatever node it points to
  • =xxx Retrieves the node with the "xxx" value, converting to string if necessary
  • [n,n] Retrieves a subset of its previous result set, implying "from, to" meaning [n1,n2>
  • @xxx Returns the first node "before" in its hierarchy that matches the given "xxx" in its name
  • n (any number) Returns the n'th child of its previous result set

Notice, you can escape iterators by using backslash "\". This allows you to look for nodes who's names are for instance "3", without using the n'th child iterator, which would defeat the purpose. In addition, you can quote iterators by using double quotes ", to allow for having iterators with values that are normally not legal within an iterator, such as /, etc. If you quote an iterator you have to quote the entire expression. Below is an example of a slightly more advanced expression.

.foo
   howdy:wo/rld
   jo:nothing
   howdy:earth

.dyn:.foo

for-each:x:@"./*/{@.dyn}/*/""=wo/rld"""

   set-value:x:@.dp/#
      :thomas was here

After evaluating the above Hyperlambda, the value of all nodes having "wo/rld" as their value inside of [.foo] will be updated to become "thomas was here". Obviously, the above expression is a ridiculous complex example, that you will probably never encounter in your own code. However, for reference purposes, let's break it down into its individual parts.

  1. Get parent node
  2. Get all children
  3. 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".
  4. Get its children
  5. Find all nodes who's value is "wo/rld".

98% of your expressions will have 1-3 iterators, no complex escaping, and no parameters. And in fact, there are thousands of lines of Hyperlambda code in Magic's middleware, and 98% of these expressions are as simple as follows.

.arguments
   foo1:string

get-value:x:@.arguments/*/foo1

Which translates into the following English.

Give me the value of any [foo1] nodes, inside of the first [.arguments] node you can find upwards in the hierarchy.

Expressions can also be extrapolated, which allows you to parametrise your expressions, by nesting expressions, substituting parts of your expression dynamically as your code is executed. Imagine the following example.

.arg1:foo2

.data
   foo1:john
   foo2:thomas
   foo3:peter

get-value:x:@.data/*/{@.arg1}

The above expression will first evaluate the {@.arg1} parts, which results in "foo2", then evaluate the outer expression, which now will look like this @.data/*/foo2.

Extending lambda expressions/iterators

You can easily extend expressions in Magic, either with a "static" iterator, implying a direct match - Or with a dynamic parametrized iterator, allowing you to create iterators that requires "parameters". To extend the supported iterators, use any of the following two static methods.

  • Iterator.AddStaticIterator - Creates a "static" iterator, implying a direct match.
  • Iterator.AddDynamicIterator - Creates a "dynamic iterator create function".

Below is a C# example, that creates a dynamic iterator, that will only return nodes having a value, that once converted into a string, has exactly n characters, not less and not more.

Iterator.AddDynamicIterator('%', (iteratorValue) => {
    var no = int.Parse(iteratorValue.Substring(1));
    return (identity, input) => {
        return input.Where(x => x.Get<string>()?.Length == no);
    };
});

var hl = @"foo
   howdy1:XXXXX
   howdy2:XXX
   howdy3:XXXXX
";
var lambda = HyperlambdaParser.Parse(hl);

var x = new Expression("../**/%3");
var result = x.Evaluate(lambda);

Notice how the iterator we created above, uses the %3 parts of the expression, to parametrize itself. If you exchange 3 with 5, it will only return [howdy1] and [howdy3] instead, since it will look for values with 5 characters instead. The Iterator class can be found in the magic.node.extensions namespace. You can use the above syntax to override the default implementation of iterators, although I wouldn't recommend it, since it would create confusion for others using your modified version.

Notice - To create an extension iterator is an exercise you will rarely if ever need to do, but is included here for reference purposes.

Parsing Hyperlambda from C#

Magic allows you to easily parse Hyperlambda from C# if you need it, which can be done as follows.

using magic.node.extensions.hyperlambda;

var hl = GetHyperlambdaAsString();
var lambda = HyperlambdaParser.Parse(hl);

The GetHyperlambdaAsString above could for instance load Hyperlambda from a file, retrieve it from your network, or some other way retrieve a snippet of Hyperlambda text. The HyperlambdaParser.Parse parts above will return your Hyperlambda as its Node equivalent. The Parser class also have an overloaded constructor for taking a Stream instead of a string.

Notice - The Node returned above will be a root node, wrapping all nodes found in your Hyperlambda as children nodes. This is necessary in order to avoid enforcing a single "document node" the way XML does. Once you have a Node object, you can easily reverse the process by using the HyperlambdaGenerator class, and its GetHyperlambda method such as the following illustrates.

using magic.node.extensions.hyperlambda;

var hl1 = GetHyperlambdaAsString();
var result = HyperlambdaParser.Parse(hl1);
var hl2 = HyperlambdaGenerator.GetHyperlambda(result.Children);

Formal specification of Hyperlambda

Notice - This part is mostly intended for developers wanting to implement their own Hyperlambda parser, and is strictly not needed to understand neither Magic nor Hyperlambda. Feel free to skip this section if it seems like Greek to you.

Hyperlambda contains 8 possible tokens in total, however since single line comments and multi line comments are interchangeable, we simplify the specification by combining these into one logical token type - And the null token isn't really an actual token, but rather a placeholder implying "absence of token". Possible logical tokens found in Hyperlambda hence becomes as follows.

  1. IND - Indent token consisting of exactly 3 SP characters.
  2. COM - Comment token. Either C style (/**/) or C++ (//) style comments.
  3. NAM - Name token declaring the name of some node.
  4. SEP - Separator token separating the name of a node from its type, and/or value.
  5. TYP - Type token declaring the type of value preceeding it. See possible types further up on page.
  6. VAL - Value token, being the value of the node.
  7. CRLF - CRLF character sequence, implying a CR, LF or CRLF. Except for inside string literals, Hyperlambda does not discriminate between and of these 3 possible combinations, and they all become interchangeable CRLF token types after parsing.
  8. NUL - Null token, implying empty or non-existing token.

Notice, a VAL and a NAM token can be wrapped inside of quotes (') or double quotes ("), like a C# string type. In addition to wrapping it inside a multiline C# type of string (@""). This allows you to declare VAL and NAM tokens with CR/LF sequences as a part of their actual value.

The formal specification of Hyperlambda is derived from combining the above 7 tokens into the following. Notice, in the following formal specification -> means "must be followed by if existing", [0..n] implies "zero to any number of repetitions", [0..1] implies "zero to 1 repetition", [1..n] implies "at least one must exist", and | implies "logical or". The paranthesis () implies a logical grouping of some token type(s), and the x= parts is an assignable variable starting at 0, optionally incremented by one for each iteration through the loop. [0..x] implies "zero to x repetitions" where x is the value of x, and [x..x+1] implies "x to x+1 number of repetitions". The = character assigns the numbers of repetitions in its RHS value to the variable x. The default number of repetitions if none are explicitly given is 1.

  1. x=0, CRLF[0..n]
  2. (COM->CRLF[1..n])[0..n]
  3. (NAM->(NUL | (SEP->VAL[0..1]) | (SEP->TYP->SEP->VAL[0..1])))[0..1]->CRLF[0..n]->(x=IND[x..x+1])
  4. GOTO 1 while not EOF

Usage

You can include the following NuGet packages into your project to use magic.node in your own projects.

  • magic.node - Core node parts
  • magic.node.extensions - Contains support for expressions, the Hyperlambda serializer and de-serializer, in addition to the typing system, plus some helper interfaces used by other parts of Magic

However, all of these packages are indirectly included when you use Magic.

Documenting nodes, arguments to slots, etc

When referencing nodes in the documentation for Magic, it is common to reference them like [this], where "this" would be the name of some node - Implying in bold characters, wrapped by square [brackets].

C# extensions

If you want to, you can easily completely exchange the underlaying file system, with your own "virtual file system", since all interaction with the physical file system is done through the IFileService and IFolderService interfaces. This allows you to circumvent the default dependency injected service, and binding towards some other implementation, at least in theory allowing you to (for instance) use a database based file system, etc. If you want to do this, you'll need to supply your own bindings to the following three interfaces, using your IoC container.

  • magic.node.contracts.IFileService
  • magic.node.contracts.IFolderService
  • magic.node.contracts.IStreamService
  • magic.node.contracts.IRootResolver

If you want to do this, you would probably want to manually declare your own implementation for these classes, by tapping into "magic.library" somehow, or not invoking its default method that binds towards the default implementation classes somehow. In addition to the above file interfaces, the following interfaces are also declared in magic.node.extensions.

  • magic.node.contracts.IMagicConfiguration - Allows you to override (parts) of the internally used configuration object
  • magic.node.contracts.IServiceCreator - Helper interface allowing you to avoid the service locator pattern, yet still dynamically create services on a per need basis from within your own C# code

Project website

The source code for this repository can be found at github.com/polterguy/magic.node, and you can provide feedback, provide bug reports, etc at the same place.

Quality gates

  • Build status
  • Quality Gate Status
  • Bugs
  • Code Smells
  • Coverage
  • Duplicated Lines (%)
  • Lines of Code
  • Maintainability Rating
  • Reliability Rating
  • Security Rating
  • Technical Debt
  • Vulnerabilities
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on magic.node:

Package Downloads
magic.signals.contracts

Contracts for magic.signals, a Super Signals implementation for Magic built on magic.node, allowing you to invoke functionality from one component in another component without any (direct) references between your components. To use package go to https://polterguy.github.io

magic.node.extensions

Expression support for magic.node, giving you expressions resembling XPath to query your magic.node graph objects, in addition to other helper methods, such as the ability to read and generate string literals, convert between types, generate and parse Hyperlambda, etc. To use package go to https://polterguy.github.io

magic.endpoint.contracts

Contracts for magic.endpoint that allows you to dynamically execute Magic endpoints, resolving to some executable piece of code. To use package go to https://polterguy.github.io

magic.node.expressions

Expression support for magic.node, giving you expressions resembling XPath to query your magic.node graph objects.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
17.2.0 2,889 1/22/2024
17.1.7 2,234 1/12/2024
17.1.6 2,150 1/11/2024
17.1.5 2,214 1/5/2024
17.0.1 2,267 1/1/2024
17.0.0 3,088 12/14/2023
16.11.5 3,343 11/12/2023
16.9.0 3,319 10/9/2023
16.7.0 4,634 7/11/2023
16.4.1 4,825 7/2/2023
16.4.0 4,636 6/22/2023
16.3.1 5,081 6/6/2023
16.3.0 4,966 5/28/2023
16.1.9 6,292 4/30/2023
15.10.11 6,122 4/13/2023
15.9.1 7,499 3/26/2023
15.9.0 7,060 3/24/2023
15.8.2 7,162 3/20/2023
15.7.0 7,694 3/6/2023
15.5.0 10,955 1/28/2023
15.2.0 9,356 1/18/2023
15.1.0 10,581 12/28/2022
14.5.7 10,466 12/13/2022
14.5.5 10,630 12/6/2022
14.5.1 10,985 11/23/2022
14.5.0 11,160 11/18/2022
14.4.5 13,290 10/22/2022
14.4.1 13,349 10/22/2022
14.4.0 13,055 10/17/2022
14.3.1 16,088 9/12/2022
14.3.0 13,898 9/10/2022
14.1.3 14,218 8/7/2022
14.1.2 13,907 8/7/2022
14.1.1 14,035 8/7/2022
14.0.14 14,345 7/26/2022
14.0.12 14,064 7/24/2022
14.0.11 13,853 7/23/2022
14.0.10 13,907 7/23/2022
14.0.9 13,981 7/23/2022
14.0.8 14,316 7/17/2022
14.0.5 13,962 7/11/2022
14.0.4 14,338 7/6/2022
14.0.3 14,182 7/2/2022
14.0.2 14,132 7/2/2022
14.0.0 14,703 6/25/2022
13.4.0 16,627 5/31/2022
13.3.4 16,181 5/9/2022
13.3.0 16,026 5/1/2022
13.2.0 15,969 4/21/2022
13.1.0 15,091 4/7/2022
13.0.0 14,519 4/5/2022
11.0.5 15,911 3/2/2022
11.0.4 14,593 2/22/2022
11.0.3 13,981 2/9/2022
11.0.2 14,662 2/6/2022
11.0.1 789 2/5/2022
11.0.0 13,663 2/5/2022
10.0.21 30,677 1/28/2022
10.0.20 14,534 1/27/2022
10.0.19 14,244 1/23/2022
10.0.18 13,707 1/17/2022
10.0.15 11,302 12/31/2021
10.0.14 8,125 12/28/2021
10.0.7 10,324 12/22/2021
10.0.5 8,627 12/18/2021
9.9.9 10,373 11/29/2021
9.9.3 10,342 11/9/2021
9.9.2 8,616 11/4/2021
9.9.0 9,076 10/30/2021
9.8.9 9,085 10/29/2021
9.8.7 9,053 10/27/2021
9.8.6 9,029 10/27/2021
9.8.5 9,067 10/26/2021
9.8.0 11,476 10/20/2021
9.7.5 19,787 10/14/2021
9.7.0 9,256 10/9/2021
9.6.6 574 8/14/2021
9.2.0 38,505 5/26/2021
9.1.4 17,633 4/21/2021
9.1.0 9,819 4/14/2021
9.0.0 9,802 4/5/2021
8.9.9 18,330 3/30/2021
8.9.3 10,104 3/19/2021
8.9.2 8,441 1/29/2021
8.9.1 8,650 1/24/2021
8.9.0 9,723 1/22/2021
8.6.9 12,374 11/8/2020
8.6.6 10,598 11/2/2020
8.6.0 13,580 10/28/2020
8.5.0 9,732 10/23/2020
8.4.0 17,671 10/13/2020
8.3.1 10,445 10/5/2020
8.3.0 8,707 10/3/2020
8.2.2 10,251 9/26/2020
8.2.1 8,626 9/25/2020
8.2.0 8,809 9/25/2020
8.1.17 19,356 9/13/2020
8.1.16 1,631 9/13/2020
8.1.15 15,244 9/12/2020
8.1.11 10,609 9/11/2020
8.1.10 9,144 9/6/2020
8.1.9 9,926 9/3/2020
8.1.8 9,064 9/2/2020
8.1.7 8,479 8/28/2020
8.1.4 8,450 8/25/2020
8.1.3 8,635 8/18/2020
8.1.2 8,679 8/16/2020
8.1.1 8,699 8/15/2020
8.1.0 852 8/15/2020
8.0.1 16,649 8/7/2020
8.0.0 8,462 8/7/2020
7.0.1 889 8/7/2020
7.0.0 15,469 6/28/2020
5.0.0 19,567 2/25/2020
4.0.4 20,148 1/27/2020
4.0.3 8,443 1/27/2020
4.0.2 8,433 1/16/2020
4.0.1 8,382 1/11/2020
4.0.0 8,414 1/5/2020
3.1.0 14,800 11/10/2019
3.0.0 26,889 10/23/2019
2.0.1 20,114 10/15/2019
2.0.0 9,369 10/13/2019
1.1.3 15,961 10/10/2019
1.1.2 26,486 10/6/2019
1.1.0 6,897 10/5/2019
1.0.0 9,370 9/26/2019