Regen at its core is a templating engine that uses a mix of C# and Python-like syntax and is entirely written in C#.
Its purpose is to replace T4 Templating with intuitive and fast in-code (not in a seperate file) scripting/templating.
Having your template alongside the generated code makes it much more readable and easy to modify leading to a major increase in productivity and maintainability.
This tutorial is about how to use Regen's templating language (aka regen-lang
) in Visual Studio but will also teach the regen-lang
in-depth.
It's also worth mentioning that there are no differences between using Regen.Core
and the VS extension
but for the #if #else #endif
that allows to place regen templates in-line in C# source code.
If you rather learn from examples, please refer to UnitTests/Examples/ (STILL WIP) or feel free to explore the unit-tests at Regen.Core.UnitTests
.
//todo nuget package for Regen.Core
Official vs-extension releases can be downloaded here
A code frame / template block inside a C# code looks like this:
#if _REGEN
//this is an input block where the template is placed.
#else
//this is where the template's output will be placed after compilation
#endif
The _REGEN
conditional compliation symbol must not be defined at any moment - making the template itself to be ignored by the C#-compiler. The #else
block is where the generated code of the compiled template is pasted by the plugin/extension.
Hello World
The percentage character (%
) signifies that the content of the following parentheses is a regen-lang
expression.
Inside foreach-loops we use hashtags (#
) (we will get to that later).
#if _REGEN
%varname = "Hello World!"
Console.WriteLine("%(varname)");
#else
Console.WriteLine("Hello World!");
#endif
Note: The following examples as of this moment will run only without any comments (//
at the end of the line).
Expressions are evaluated using Flee and are C# compliant.
Syntax: %(expr)
Syntax in-foreach: #(expr)
What can I write inside an expression? Anything that works with Flee which covers mostly what works in C#.
%(1 + 1) //returns 2 (int)
%(1 + 1.1f) //returns 2.1 (upcasted float)
%(1 + 1.1) //returns 2.1 (upcasted double)
%arr = [1,2,]
%(arr[0] + arr[1]) //returns 3
%(arr[0] + arr[arr.Length-1]) //returns 3, arrays impl IList, remember?
%str = "there"
%("hi" + " " + str) //returns "hi there"
%("hi" + " " + str + str[2]) //returns "hi theree"
Not supported features: new
, throw
, explicit casting, as
, is
, ??
, ternery expressions, generics, sizeof
, await
Regen has it's own type system (See More) with an abstract base class Data.
To the user it is mostly transparent.
Syntax: %name = expr
Note: The template is compiled from top to bottom therefore you must first declare variables and then use them.
//primitives
%an_int = 1
%a_long = 100000L
%a_float = 1f //or 1.0f
%a_double = 1d //or 1.0d
%a_decimal = 5.321m
%name = "string literal"
%a_char = "c"
%array = [1,2,3,4, "woah, a string"] //arrays are non-generic.
%dictionary = [key: 1, a_key2: 2, "kk": 3] //dictionariy is declared as an array of key-value pairs
Foreach loops in regen-lang
allows to iterate a single list/array or multiple lists/arrays at the same time.
An important difference is that inside the body of a foreach
expression you must use of the hashtag (#
) instead of the percentage (%
) to signify expressions.
The synax specification:
multiline:
%foreach expr, expr ..., exprn%
template
%
singleline:
%foreach expr1, expr2 ..., exprn
template
expr - an expression that returns an object that must implement IList
template - a regen template (using # instead of % for expressions)
As you can see, unlike traditional foreach/for - we can pass multiple expressions.
Every expression that returns an IList will be iterated together to the smallest length of them all.
We access the list values inside the foreach template in regen-lang
using the loop variables #1
, #2
where #1
accesses the first expression's current value and #2
accesses second expression's current value and so forth.
The following example shows how a foreach loop iterating over multiple lists plain C#
code and how it can be rewritten in regen
:
//assume expr1 and expr2 are an IList
foreach (var tuple in System.Linq.Enumerable.Zip(expr1, expr2, (val1, val2) => (val1, val2))) {
Console.WriteLine($"! expr1: {tuple.val1}, expr2: {tuple.val2}");
}
Heres how we would implement the same in regen-lang
with a multi-variable foreach-expression:
%foreach expr1, expr2%
Console.WriteLine($"! expr1: #1, expr2: #2");
%
Note: The engine respects indentation so the template's indentation will be kept in the generated code.
Let's try something less basic, lets iterate an array we defined.
#if _REGEN
%numbers = [1,2,3] //or range(1,3)
%foreach numbers%
Console.WriteLine("#1");
%
some text that has nothing to do with the foreach
#else
Console.WriteLine("1");
Console.WriteLine("2");
Console.WriteLine("3");
some text that has nothing to do with the foreach
#endif
Note: In case you want to actually write #
in your template (or %
), use the backslash \
to escape them: (//TODO STILL WIP)
#if _REGEN
%foreach range(1,3)
Console.Writeline("\#1 = #1");
#else
Console.Writeline("#1 = 1");
Console.Writeline("#1 = 2");
Console.Writeline("#1 = 3");
#endif
What if we want to know our current index (usually i
) like in a for-loop?
For this we have a reserved variable named i
. When-ever you use it, it'll hold the value of the current iteration index (0 based).
The usage of i
must be inside an expression-block like this: #(i)
Note: If you'll try to declare a variable named i
, it'll throw a compilation error.
#if _REGEN
%foreach range(1,3)
Console.Writeline("\#(i) = #(i), \#1 = #1");
#else
Console.Writeline("#(i) = 0, #1 = 1");
Console.Writeline("#(i) = 1, #1 = 2");
Console.Writeline("#(i) = 2, #1 = 3");
#endif
Consider the following example:
%foreach ["a", "b", "c"], ["e", "f", "g"]%
%foreach ["A","B","C"], ["E","F","G"]%
%foreach ["1","2","3"], ["4","5","6"]%
//#1 [a-c], #2 [e-g] //#101 [A-C], #102 [E-G] //#201 [1-3], #202 [4-6]
%
%
%
Output:
//a [a-c], e [e-g] //A [A-C], E [E-G] //1 [1-3], 4 [4-6]
//a [a-c], e [e-g] //A [A-C], E [E-G] //2 [1-3], 5 [4-6]
//a [a-c], e [e-g] //A [A-C], E [E-G] //3 [1-3], 6 [4-6]
//a [a-c], e [e-g] //B [A-C], F [E-G] //1 [1-3], 4 [4-6]
//a [a-c], e [e-g] //B [A-C], F [E-G] //2 [1-3], 5 [4-6]
//a [a-c], e [e-g] //B [A-C], F [E-G] //3 [1-3], 6 [4-6]
//a [a-c], e [e-g] //C [A-C], G [E-G] //1 [1-3], 4 [4-6]
//a [a-c], e [e-g] //C [A-C], G [E-G] //2 [1-3], 5 [4-6]
//a [a-c], e [e-g] //C [A-C], G [E-G] //3 [1-3], 6 [4-6]
//b [a-c], f [e-g] //A [A-C], E [E-G] //1 [1-3], 4 [4-6]
//b [a-c], f [e-g] //A [A-C], E [E-G] //2 [1-3], 5 [4-6]
//b [a-c], f [e-g] //A [A-C], E [E-G] //3 [1-3], 6 [4-6]
//b [a-c], f [e-g] //B [A-C], F [E-G] //1 [1-3], 4 [4-6]
//b [a-c], f [e-g] //B [A-C], F [E-G] //2 [1-3], 5 [4-6]
//b [a-c], f [e-g] //B [A-C], F [E-G] //3 [1-3], 6 [4-6]
//b [a-c], f [e-g] //C [A-C], G [E-G] //1 [1-3], 4 [4-6]
//b [a-c], f [e-g] //C [A-C], G [E-G] //2 [1-3], 5 [4-6]
//b [a-c], f [e-g] //C [A-C], G [E-G] //3 [1-3], 6 [4-6]
//c [a-c], g [e-g] //A [A-C], E [E-G] //1 [1-3], 4 [4-6]
//c [a-c], g [e-g] //A [A-C], E [E-G] //2 [1-3], 5 [4-6]
//c [a-c], g [e-g] //A [A-C], E [E-G] //3 [1-3], 6 [4-6]
//c [a-c], g [e-g] //B [A-C], F [E-G] //1 [1-3], 4 [4-6]
//c [a-c], g [e-g] //B [A-C], F [E-G] //2 [1-3], 5 [4-6]
//c [a-c], g [e-g] //B [A-C], F [E-G] //3 [1-3], 6 [4-6]
//c [a-c], g [e-g] //C [A-C], G [E-G] //1 [1-3], 4 [4-6]
//c [a-c], g [e-g] //C [A-C], G [E-G] //2 [1-3], 5 [4-6]
//c [a-c], g [e-g] //C [A-C], G [E-G] //3 [1-3], 6 [4-6]
Structure: Every foreach in the example above has two arrays. the arrays are iterated simultaneously in a zipped manner with the same i
index as can be seen in the output above. this is useful if you have two arrays that are needed coupled; e.g. name and age.
Accessing: The first foreach arrays are access by #xx, second is accessed by %1xx, third will be accessed by %2xx.
#if _REGEN
%foreach [1,2,3,4]%
var name#1 = #(i * #1);
%
#else
var name1 = 0;
var name2 = 2;
var name3 = 6;
var name4 = 12;
#endif
#if _REGEN
using System;
%types = ["short","int","long"]
%foreach types%
public #1 Multiply#(#1.ToUpper())(#1 left, #1 right) {
return (#1) Convert.ChangeType(left * right, typeof(#1));
}
%
#else
using System;
public short MultiplySHORT(short left, short right) {
return (short) Convert.ChangeType(left * right, typeof(short));
}
public int MultiplyINT(int left, int right) {
return (int) Convert.ChangeType(left * right, typeof(int));
}
public long MultiplyLONG(long left, long right) {
return (long) Convert.ChangeType(left * right, typeof(long));
}
#endif
Importing allows the user to use external functions available in expression evaluation.
Syntax 1: %import namespace.type
Syntax 2: %import namespace.type as aliasname
Syntax 3 (WIP): %import global "./directory/file.cs"
Importing static functions is fairly easy.
Any static function that is imported becomes available in expressions as a lowercase.
Therefore Math.Sin(double)
turns usable %(sin(double))
.
If the developer uses syntax 2, aliasname is used as a prefix and should look like the following:
%import System.Math as math
%a = math.cos(1)
By default there are couple of namespaces imported,
One of them is System.Math
so using Math.Cos(...)
will be in regen-lang
: cos(1)
System.Math
Regen.Builtins.CommonRandom as random
Regen.Builtins.CommonRegex
(WIP)Regen.Builtins.CommonLinq
(WIP)except(list, params objs)
- returnslist
except for items passed viaobjs
concat(list, params objs)
- Concatenate allobjs
withlist
and returns a new array
Regen.Builtins.CommonExpressionFunctions
forevery(IList, IList, bool exclude)
- Combine the two lists to mimic a nested for loop- Example: forevery([1,2,3], [3,4], false) - will return: [1,1,2,2,3,3], [3,4,3,4,3,4].
- exclude will compare the items of current iteration and if they match, it'll skip it.
- Example: forevery([1,2,3], [3,4], true) - will return: [1,1,2,2,3], [3,4,3,4,4]. (notice the missing element)
len(ICollection)
- Returns the lenght of given collection (IList
implementsICollection
)range(length)
- - Returns an array of integer numbers (similar to Enumerable.Range)range(startFrom, length)
- Returns an array of integer numbers (similar to Enumerable.Range)str(obj)
- Performsobj?.ToString() ?? ""
str(params objs)
- Converts allobjs
to string and then concatenates them.asarray(params obj)
- Wraps all parameters passed to an Array.isnull(obj)
- Checks ifobj
is C#null
orregen-lang
null.isarray(obj)
- Checks ifobj
implementsIList
isnumber(obj)
- Checks ifobj
is a numeric type.repeat("expr", int repeats, string seperator, ...)
- Repeat expr repeats times inlined.
-
Inline Repeat
repeat(string expr, int repeats, string seperator, string beforeFirst, string afterFirst,
string beforeLast, string afterLast)
Repeat is especially useful when you need to output the same repeating word in the same line.expr
andseperator
can be an expression if the string starts with^
. (Escaped version:\^
)
Internally occurs a for-loop. its index is available as a variablen
(0 based).Example:
#if _REGEN
%pre = "dims.Item"
%foreach range(2,14)%
public static implicit operator Shape(#(repeat("int", #1 , ", " , "(" , "" , "" , ")" )) dims) => new Shape(#(repeat("^pre+(n+1)", #1 , ", " )));
%
#else
public static implicit operator Shape((int, int) dims) => new Shape(dims.Item1, dims.Item2);
public static implicit operator Shape((int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3);
public static implicit operator Shape((int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4);
public static implicit operator Shape((int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5);
public static implicit operator Shape((int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6);
public static implicit operator Shape((int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7);
public static implicit operator Shape((int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10, dims.Item11);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10, dims.Item11, dims.Item12);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10, dims.Item11, dims.Item12, dims.Item13);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10, dims.Item11, dims.Item12, dims.Item13, dims.Item14);
public static implicit operator Shape((int, int, int, int, int, int, int, int, int, int, int, int, int, int, int) dims) => new Shape(dims.Item1, dims.Item2, dims.Item3, dims.Item4, dims.Item5, dims.Item6, dims.Item7, dims.Item8, dims.Item9, dims.Item10, dims.Item11, dims.Item12, dims.Item13, dims.Item14, dims.Item15);
#endif
__context__
|Flee.PublicTypes.ExpressionContext
Returns Flee's expression context.__vars__
|Flee.PublicTypes.VariableCollection
Returns to Flee's expression context variables storage wrapped inVariableCollectionWrapper
.__compiler__
|Regen.Compiler.RegenCompiler
Returns the interpreter that the expression is currently running in.
In some cases there is a need for a shared varaible across an entire solution, project or single file.
_REGEN_GLOBAL
and *.regen
filetype were created.
Is precompiled before any _REGEN
block in that specific file, making %arr
available
in the other _REGEN
blocks/frames.
#if _REGEN_GLOBAL
%arr = [1,2,3]
#endif
#if _REGEN
%(arr[2])
#else
3
#endif
#if _REGEN
%(arr[1])
#else
2
#endif
*.regen
files are used to provide variables solution-wide and has to be reloaded from the Regen menu (by Reload Globals
button) after changes.
The contents of the entire .regen
file are considered regen-lang
therefore there is no need for #if blocks.
Example file: /sharedtypes.regen
%numericalTypes = ["int", "short"]
%complexTypes = ["object", "string"]
%allTypes = concat(numericalTypes,complexTypes)
In a different file:
#if _REGEN
%(allTypes[0])
#else
int
#endif
Once you'll reload globals, these variables will be available accross and _REGEN
and _REGEN_TEMPLATE
blocks in this solution.
Regen file templating gives the ability to generate multiple files.
#if _REGEN_TEMPLATE
%template "relative path" for every expr1, expr2 ... , expr-n
#endif
... file contents ...
relative path - may contain #n to access current data relative to the template file.
file contents - regular C# code, any `__n__` will be handled like `#n` inside a foreach.
expr - an expression that returns an object that must implement IList
#if _REGEN_TEMPLATE
%template "./#2/filename.#1.cs" for every ["INT", "FLOAT"], ["int", "float"]
#endif
public class Convert__1__ { }
First file (out of 2) will output as "./int/filename.INT.cs"
relative to the template file path.
__n__
are similar to #n
inside a foreach loop resulting the first file as:
public class ConvertINT { }
Example file: test/Regen.Core.UnitTest/Package/tempfilename.template.cs
The logic-flow is as follows:
- The template file is compiled and all
__n__
literals are replaced with their corresponding value. - Every template file outputted goes through
Compile File
command triggering compilation at all_REGEN
blocks - The files are saved path mentioned in the
%template
expression relatively to the template file itself.