Skip to content

Commit

Permalink
Merge pull request #17 from Stillpoint-Software/develop
Browse files Browse the repository at this point in the history
Initial Release v1.0.0 🎉
  • Loading branch information
MattEdwardsWaggleBee authored Dec 5, 2024
2 parents 5c0ac39 + 94a136e commit 19398a8
Show file tree
Hide file tree
Showing 72 changed files with 4,589 additions and 3,162 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
- develop

env:
DOTNET_VERSION: "8.0.x"
DOTNET_VERSION: "9.0.x"

jobs:
format:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SOLUTION_NAME: ${{ vars.SOLUTION_NAME }}
DOTNET_VERSION: "8.0.x"
DOTNET_VERSION: "9.0.x"

jobs:
test:
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
-->
<Target Name="PushPackage" AfterTargets="Pack"
Condition="'$(PushAfterPack)'=='true' AND '$(IsPackable)'=='true'">
<Exec Command="dotnet nuget push $(SolutionDir)output/$(TargetName).$(PackageVersion).nupkg $(PackageSourceParam) $(PackageApiKeyParam)"></Exec>
<Exec Command="dotnet nuget push $(SolutionDir)output/$(PackageId).$(PackageVersion).nupkg $(PackageSourceParam) $(PackageApiKeyParam)"></Exec>
</Target>

<!--
Expand Down
6 changes: 5 additions & 1 deletion Hyperbee.Expressions.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="__" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="__" Suffix="" Style="aa_bb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Awaiters/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Castclass/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=comparand/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fingerprinter/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gotos/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hyperbee/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ldarg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ldarga/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ldarga/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=optimizer_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
47 changes: 22 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Welcome to Hyperbee Expressions

`Hyperbee.Expressions` is a library for creating c# expression trees that extend the capabilities of standard expression
trees to handle asynchronous workflows and other constructs.
`Hyperbee.Expressions` is a C# library that extends the capabilities of expression trees to handle asynchronous
workflows and other language constructs.

## Features

Expand All @@ -17,6 +17,10 @@ trees to handle asynchronous workflows and other constructs.
* `ForExpression`: An expression that represents a for loop.
* `ForEachExpression`: An expression that represents a foreach loop.

* * **Other Expressions**
* `StringFormatExpression`: An expression that creates a string using a supplied format string and parameters.
* `DebugExpression`: An expression that helps when debugging expression trees.

* Supports Fast Expression Compiler (FEC) for improved performance.

## Examples
Expand All @@ -25,41 +29,33 @@ trees to handle asynchronous workflows and other constructs.

The following example demonstrates how to create an asynchronous expression tree.

When the expression tree is compiled, the `BlockAsyncExpression` will auto-generate a state machine that executes
`AwaitExpressions` in the block asynchronously.

```csharp

public class AsyncExample
{
public async Task ExampleAsync()
{
// Variables to store the results
var result1 = Expression.Variable( typeof(int), "result1" );
var result2 = Expression.Variable( typeof(int), "result2" );

// Define two async method calls
var instance = Expression.Constant( this );

var awaitExpr1 = ExpressionExtensions.Await(
Expression.Call( instance, nameof(FirstAsyncMethod), Type.EmptyTypes )
);

var awaitExpr2 = ExpressionExtensions.Await(
Expression.Call( instance, nameof(SecondAsyncMethod), Type.EmptyTypes, result1 )
);
// Create an async block that calls async methods and assigns their results
// Assign the results of the await expressions to the variables
var assignResult1 = Expression.Assign( result1, awaitExpr1 );
var assignResult2 = Expression.Assign( result2, awaitExpr2 );
var instance = Constant( this );
var result1 = Variable( typeof(int), "result1" );
var result2 = Variable( typeof(int), "result2" );

// Create an async block that calls both methods and assigns their results
var asyncBlock = AsyncExpression.BlockAsync(
var asyncBlock = BlockAsync(
[result1, result2],
assignResult1,
assignResult2
Assign( result1, Await(
Call( instance, nameof(FirstAsyncMethod), Type.EmptyTypes )
) ),
Assign( result2, Await(
Call( instance, nameof(SecondAsyncMethod), Type.EmptyTypes, result1 )
) )
);

// Compile and execute the async block
var lambda = Expression.Lambda<Func<Task<int>>>( asyncBlock );
var lambda = Lambda<Func<Task<int>>>( asyncBlock );
var compiledLambda = lambda.Compile();
var resultValue2 = await compiledLambda();

Expand Down Expand Up @@ -119,6 +115,7 @@ public class UsingExample
Special thanks to:

- Sergey Tepliakov - [Dissecting the async methods in C#](https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/).
- [Fast Expression Compiler](https://github.com/dadhi/FastExpressionCompiler) for improved performance.
- [Just The Docs](https://github.com/just-the-docs/just-the-docs) for the documentation theme.

## Contributing
Expand Down
15 changes: 4 additions & 11 deletions docs/.todo.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
# Things TODO

## Work to be done
## AsyncEnumerable
- Add support for `IAsyncEnumerable`

- Investigate alternative StateMachine implementation that would allow us to use a struct instead of a class for the state machine.

## State machine Fields
- Are we over adding fields to the StateMachine?
- Look into shared Exceptions object
## State machine fields
- Look into shared Exceptions objects
- Add shared awaiters based on common types

## Documentation
- Add fast compile documation
- Find/Document known limitations
- Unsupported `Await` in SwitchCase TestValues
- Unsupported `Await` in Finally blocks

46 changes: 21 additions & 25 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ nav_order: 1
# Welcome to Hyperbee Expressions

`Hyperbee.Expressions` is a library for creating c# expression trees that extend the capabilities of standard expression
trees to handle asynchronous workflows and other constructs.
trees to handle asynchronous workflows and other language constructs.

## Features

Expand All @@ -22,6 +22,10 @@ trees to handle asynchronous workflows and other constructs.
* `ForExpression`: An expression that represents a for loop.
* `ForEachExpression`: An expression that represents a foreach loop.

* **Other Expressions**
* `StringFormatExpression`: An expression that creates a string using a supplied format string and parameters.
* `DebugExpression`: An expression that helps when debugging expression trees.

* Supports Fast Expression Compiler (FEC) for improved performance.

## Examples
Expand All @@ -30,41 +34,32 @@ trees to handle asynchronous workflows and other constructs.

The following example demonstrates how to create an asynchronous expression tree.

When the expression tree is compiled, the `BlockAsyncExpression` will auto-generate a state machine that executes
`AwaitExpressions` in the block asynchronously.

```csharp

public class AsyncExample
{
public async Task ExampleAsync()
{
// Variables to store the results
var result1 = Expression.Variable( typeof(int), "result1" );
var result2 = Expression.Variable( typeof(int), "result2" );

// Define two async method calls
var instance = Expression.Constant( this );

var awaitExpr1 = ExpressionExtensions.Await(
Expression.Call( instance, nameof(FirstAsyncMethod), Type.EmptyTypes )
);

var awaitExpr2 = ExpressionExtensions.Await(
Expression.Call( instance, nameof(SecondAsyncMethod), Type.EmptyTypes, result1 )
);

// Assign the results of the await expressions to the variables
var assignResult1 = Expression.Assign( result1, awaitExpr1 );
var assignResult2 = Expression.Assign( result2, awaitExpr2 );
// Create an async block that calls async methods and assigns their results
var instance = Constant( this );
var result1 = Variable( typeof(int), "result1" );
var result2 = Variable( typeof(int), "result2" );

// Create an async block that calls both methods and assigns their results
var asyncBlock = AsyncExpression.BlockAsync(
var asyncBlock = BlockAsync(
[result1, result2],
assignResult1,
assignResult2
Assign( result1, Await(
Call( instance, nameof(FirstAsyncMethod), Type.EmptyTypes )
) ),
Assign( result2, Await(
Call( instance, nameof(SecondAsyncMethod), Type.EmptyTypes, result1 )
) )
);

// Compile and execute the async block
var lambda = Expression.Lambda<Func<Task<int>>>( asyncBlock );
var lambda = Lambda<Func<Task<int>>>( asyncBlock );
var compiledLambda = lambda.Compile();
var resultValue2 = await compiledLambda();

Expand Down Expand Up @@ -124,6 +119,7 @@ public class UsingExample
Special thanks to:

- Sergey Tepliakov - [Dissecting the async methods in C#](https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/).
- [Fast Expression Compiler](https://github.com/dadhi/FastExpressionCompiler) for improved performance. :heart:
- [Just The Docs](https://github.com/just-the-docs/just-the-docs) for the documentation theme.

## Contributing
Expand Down
6 changes: 3 additions & 3 deletions docs/state-machine/lowering-visitor.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ It also identifies variables that will need to be hoisted so that variable scope

## Introduction

The `LoweringVisitor` is responsible for transforming an expression tree into discrete states, and transitions, that will be used
The `LoweringVisitor` is responsible for transforming an expression tree into discrete states and transitions, that will be used
to generate the final state machine.

The purpose of this visitor is to "lower" high-level constructs, such as `await`, `if/else`, `switch`, and loops, into individual
`NodeExpression` objects that the state machine can later process.
The purpose of this visitor is to "lower" high-level constructs, such as `await`, `if/else`, `switch`, `try\catch`, and loops,
into individual `NodeExpression` objects that the state machine can later process.


## 1. Handling Control Flow Constructs (Branching and Loops)
Expand Down
7 changes: 3 additions & 4 deletions docs/state-machine/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ operations that must suspend and resume execution.
State machine creation occurs in two passes:

### Pass 1: Expression Tree Transformation
The first pass uses a Lowering Technique to transform flow control constructs (such as if, switch, loops, and awaits) into a
state tree that can be used to generate a flattened goto state machine. This step systematically traverses the expression tree
and replaces branching constructs with state nodes that manage control flow using transitions and goto operations. This step also
identifies variables that persist across state transitions and which will need to be hoisted by the builder.
The first pass uses a Lowering Technique to transform `BlockAsyncExpression`s into state trees, and handles the lowering of
complex flow control constructs (ifs, switches, loops, try/catch, and awaits) into more primitive representations. This step
also identifies variables that persist across states and require hoisting.

### Pass 2: State Machine Builder
The second pass builds the state machine based on the transformed structure. This involves creating a state-machine type,
Expand Down
Loading

0 comments on commit 19398a8

Please sign in to comment.