Skip to content

Instantly share code, notes, and snippets.

@dekrain
Last active September 15, 2022 20:14
Show Gist options
  • Save dekrain/c82c537ab486e36215b7c1f1fd0d204f to your computer and use it in GitHub Desktop.
Save dekrain/c82c537ab486e36215b7c1f1fd0d204f to your computer and use it in GitHub Desktop.
CLI Custom Attribute Virtual Machine: The best worst idea I had in 3 months
// Released under GNU GPLv3 license
// https://www.gnu.org/licenses/gpl-3.0.en.html
// (©) Copyright DeKrain 2022
// The example program is located near the end of this file
AttrVM.Executor.CompileAndRunMainProgram(@"MainProgram.Program");
#region VM Implementation
namespace AttrVM {
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using static Constants;
using static OperandStackValueTypeHelpers;
public class BadProgramException : ApplicationException {
public BadProgramException(string Message)
: base(Message) {}
}
#region Base Attributes
static class Constants {
public const AttributeTargets ActionTargets = AttributeTargets.Property | AttributeTargets.Class;
}
[AttributeUsage(AttributeTargets.Class)]
public abstract class ExecutionPointAttribute : Attribute {}
[AttributeUsage(ActionTargets)]
public abstract class SeqPointAttribute : Attribute {}
[AttributeUsage(ActionTargets)]
public abstract class ActionAttribute : Attribute {}
public abstract class ComputationAttribute : ActionAttribute {}
#endregion
#region Execution Point Attributes
public class EntryPointAttribute : ExecutionPointAttribute {
public string Symbol { get; set; }
public EntryPointAttribute(string Symbol) {
this.Symbol = Symbol;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class SubroutineArgumentsAttribute : Attribute {
public string[] Args { get; set; }
public SubroutineArgumentsAttribute(string[] Args) {
this.Args = Args;
}
}
#endregion
#region Sequence Points
public class SeqFlowNextAttribute : SeqPointAttribute {
public string Symbol { get; set; }
public SeqFlowNextAttribute(string Symbol) {
this.Symbol = Symbol;
}
}
public class SeqFlowBranchAttribute : SeqPointAttribute {
public string OnTrue { get; set; }
public string OnFalse { get; set; }
}
public class SeqFlowHaltAttribute : SeqPointAttribute {
public int Status { get; set; } = 0;
}
public class SubroutineResultAttribute : SeqPointAttribute {
public string Variable { get; set; }
public SubroutineResultAttribute(string Variable = null) {
this.Variable = Variable;
}
}
#endregion
#region Regular Actions
public class OutputStringAttribute : ActionAttribute {
public string String { get; set; }
public OutputStringAttribute(string String) {
this.String = String;
}
}
public class OutputVariableAttribute : ActionAttribute {
public string Variable { get; set; }
public bool Newline = false;
public OutputVariableAttribute(string Variable) {
this.Variable = Variable;
}
}
public class InputValueAttribute : ActionAttribute {
public string Variable { get; set; }
public string Prompt { get; set; }
public InputValueAttribute(string Variable) {
this.Variable = Variable;
}
}
public class InvokeSubroutineAttribute : ActionAttribute {
public string TargetRoutine { get; set; }
public string[] Args { get; set; }
public string Result { get; set; }
public InvokeSubroutineAttribute(string TargetRoutine) {
this.TargetRoutine = TargetRoutine;
}
}
public class AssignVariable : ActionAttribute {
public string Variable { get; set; }
public object Value { get; set; }
public AssignVariable(string Variable, object Value) {
this.Variable = Variable;
this.Value = Value;
}
}
#endregion
#region Computations
[AttributeUsage(ActionTargets)]
public class ComputeFlowAttribute : Attribute {}
public class ComputeNextAttribute : ComputeFlowAttribute {
public string Symbol { get; set; }
public ComputeNextAttribute(string Symbol) {
this.Symbol = Symbol;
}
}
/// <summary>Signifies end of resultless computation</summary>
[AttributeUsage(AttributeTargets.Property)]
public class ComputeDoneAttribute : ComputeFlowAttribute {}
/// <summary>Signifies end of a single-result computation</summary>
[AttributeUsage(AttributeTargets.Property)]
public class ComputeResultAttribute : ComputeFlowAttribute {}
[Flags]
public enum StackBehaviorOptions {
/// <summary>Don't pop top value(s) from the stack</summary>
NoPop = 1,
}
[AttributeUsage(ActionTargets)]
public class StackBehaviorAttribute : Attribute {
public StackBehaviorOptions Options { get; set; }
public StackBehaviorAttribute(StackBehaviorOptions Options) {
this.Options = Options;
}
}
// ================================
/// <summary>Push variable value on the stack</summary>
public class LoadVariableAttribute : ComputationAttribute {
public string Variable { get; set; }
public LoadVariableAttribute(string Variable) {
this.Variable = Variable;
}
}
/// <summary>Like <see cref="LoadVariableAttribute"/>, but push the address instead</summary>
public class LoadVariableAddressAttribute : ComputationAttribute {
public string Variable { get; set; }
public LoadVariableAddressAttribute(string Variable) {
this.Variable = Variable;
}
}
/// <summary>Push constant integer on the stack</summary>
public class LoadConstantAttribute : ComputationAttribute {
public ulong Value { get; set; }
private Type type_;
public Type Type { get => type_; set {
if (!value.IsPrimitive)
throw new InvalidCastException("A contant must have an integer type");
type_ = value;
} }
public LoadConstantAttribute(ulong Value, Type Type) {
this.Value = Value;
this.Type = Type;
}
public LoadConstantAttribute(object value) {
this.Value = Convert.ToUInt64(value);
this.Type = value.GetType();
}
}
/// <summary>Dereference a value from address on top of the stack</summary>
/// <remarks>Pops the address from the stack unless <see cref="StackBehaviorAttributes.NoPop"/> is specified.</remarks>
public class DereferenceValueAttribute : ComputationAttribute {}
/// <summary>Pop a value from the stack and store it in address on top of the stack</summary>
/// <remarks>Pops the value and address from the stack unless <see cref="StackBehaviorAttributes.NoPop"/> is specified.</remarks>
public class StoreDerefValueAttribute : ComputationAttribute {}
public enum Comparison {
Equals,
NotEquals,
LessThan,
GreaterThan,
LessOrEqualTo,
GreaterOrEqualTo,
}
/// <summary>Pop two values from the stack and compare then according to specified <see cref="Comparison"/>, then push the result on the stack</summary>
public class CompareValuesAttribute : ComputationAttribute {
public Comparison Comparison { get; set; }
public CompareValuesAttribute(Comparison Comparison) {
this.Comparison = Comparison;
}
}
/// <summary>Convert the integer on top of the stack to a boolean value</summary>
public class ConvertToBooleanAttribute : ComputationAttribute {}
/// <summary>Pop two values from the stack and add them, push result on the stack</summary>
/// <remarks>All values are interpreted left-to-right in order of pushing.</remarks>
public class AddValuesAttribute : ComputationAttribute {}
/// <summary>Pop two values from the stack and subtract them, push result on the stack</summary>
/// <remarks>All values are interpreted left-to-right in order of pushing.</remarks>
public class SubtractValuesAttribute : ComputationAttribute {}
/// <summary>Pop two values from the stack and multiply them, push result on the stack</summary>
/// <remarks>All values are interpreted left-to-right in order of pushing.</remarks>
public class MultiplyValuesAttribute : ComputationAttribute {}
/// <summary>Pop two values from the stack and divide them, push result on the stack</summary>
/// <remarks>All values are interpreted left-to-right in order of pushing.</remarks>
public class DivideValuesAttribute : ComputationAttribute {}
/// <summary>Pop two values from the stack and divide them and take the remainder, push result on the stack</summary>
/// <remarks>All values are interpreted left-to-right in order of pushing.</remarks>
public class DivideModuleValuesAttribute : ComputationAttribute {}
#endregion
//// Now for the actual fun stuff...
public static class Executor {
public static void CompileAndRunMainProgram(string TypeName) {
var resolver = new StaticResolver();
var sub = resolver.ResolveName(TypeName);
if (sub.HasParameters)
throw new BadProgramException("Entry point must be a subroutine with no parameters");
resolver.CompileProgram();
sub.Run();
}
}
/// <summary>Keeps track of global symbols like types (unimpl.) and subroutines</summary>
public class StaticResolver {
private Dictionary<string, Compiler> ResolvedNames = new();
public Compiler ResolveName(string TypeName) {
var type = Type.GetType(TypeName);
if (type == null)
throw new TypeLoadException("Type not found");
var entry = (ExecutionPointAttribute)Attribute.GetCustomAttribute(type, typeof(ExecutionPointAttribute));
if (entry == null)
throw new BadProgramException("Type is not an execution unit");
if (entry is EntryPointAttribute ep) {
var compiler = new Compiler(this, type);
ResolvedNames.Add(TypeName, compiler);
compiler.CompileEntryPoint(ep.Symbol);
return compiler;
} else {
throw new NotSupportedException("Execution type not supported");
}
}
public void CompileProgram() {
foreach (var method in ResolvedNames.Values) {
method.GenerateBytecode();
}
}
}
/// <summary>Handles compilation of one program unit (subroutine)</summary>
public class Compiler {
public readonly Type Target;
public readonly StaticResolver Root;
public Type[] ParameterSignature { get; }
public bool HasParameters => ParameterSignature.Length != 0;
public Type ResultType { get; private set; }
private FlowAction EntryPoint;
private HashSet<string> ResolvedNames = new();
private Dictionary<string, Variable> ResolvedVariables = new();
private Dictionary<string, FlowAction> ResolvedActions = new();
public Compiler(StaticResolver root, Type type) {
Root = root;
Target = type;
var paramSig = (SubroutineArgumentsAttribute)Attribute.GetCustomAttribute(type, typeof(SubroutineArgumentsAttribute));
if (paramSig?.Args != null) {
ParameterSignature = new Type[paramSig.Args.Length];
int idx = 0;
foreach (string Name in paramSig.Args) {
var Var = ResolveVariable(Name);
ParameterSignature[idx] = Var.Type;
++idx;
Var.ParamIndex = idx;
}
} else {
ParameterSignature = Array.Empty<Type>();
}
}
public void CompileEntryPoint(string Symbol) {
if (EntryPoint != null)
throw new InvalidOperationException("Entry point already declared");
EntryPoint = ResolveAction(Symbol);
ResultType ??= typeof(void);
}
private MemberInfo ResolveMember(string Name) {
if (ResolvedNames.Contains(Name))
throw new BadProgramException($"Name {Name} declared as a different entity");
var memb = Target.GetMember(Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (memb.Length != 1)
throw new BadProgramException("Expected one member of declared name");
if (!(MemberTypes.Property | MemberTypes.NestedType).HasFlag(memb[0].MemberType))
throw new BadProgramException("Program entity must be a property or a class");
ResolvedNames.Add(Name);
return memb[0];
}
private Variable ResolveVariable(string Name) {
if (ResolvedVariables.TryGetValue(Name, out var Var))
return Var;
var memb = ResolveMember(Name);
if (memb.MemberType != MemberTypes.Property)
throw new BadProgramException("A data member must be a property");
if (Attribute.IsDefined(memb, typeof(SeqPointAttribute)) || Attribute.IsDefined(memb, typeof(ActionAttribute)))
throw new BadProgramException("A data member cannot have execution attributes");
var prop = (PropertyInfo)memb;
Var = new Variable(Name, prop.PropertyType, prop, (prop.CanRead ? VariableFlags.Readable : 0) | (prop.CanWrite ? VariableFlags.Writable : 0));
ResolvedVariables.Add(Name, Var);
return Var;
}
private FlowAction ResolveAction(string Name) {
if (ResolvedActions.TryGetValue(Name, out var Act))
return Act;
var memb = ResolveMember(Name);
var seq = (SeqPointAttribute)Attribute.GetCustomAttribute(memb, typeof(SeqPointAttribute));
if (seq == null)
throw new BadProgramException("An instruction must have a seq flow attribute");
var action = (ActionAttribute)Attribute.GetCustomAttribute(memb, typeof(ActionAttribute));
Act = new FlowAction(memb, seq, action);
ResolvedActions.Add(Name, Act);
switch (seq) {
case SeqFlowNextAttribute next:
Act.Next = ResolveAction(next.Symbol);
break;
case SeqFlowBranchAttribute branch:
Act.BranchTrue = ResolveAction(branch.OnTrue);
Act.BranchFalse = ResolveAction(branch.OnFalse);
break;
case SeqFlowHaltAttribute halt: break;
case SubroutineResultAttribute result: {
Act.ResultVar = result.Variable == null ? null : ResolveVariable(result.Variable);
var type = Act.ResultVar?.Type ?? typeof(void);
if (this.ResultType == null)
this.ResultType = type;
else if (!this.ResultType.Equals(type))
throw new InvalidCastException("Subroutine result type mismatch");
break;
}
default:
throw new NotSupportedException("Flow kind not supported");
}
if (action is ComputationAttribute comp) {
if (memb.MemberType != MemberTypes.NestedType)
throw new BadProgramException("Computation node must be a class");
Act.Computation = ResolveExpression(comp, (Type)memb);
} else if (action != null) {
Act.Action = ResolveStatement(action);
}
if (seq is SeqFlowBranchAttribute) {
if (Act.Computation?.ResultType == null || !Act.Computation.ResultType.Equals(typeof(bool)))
throw new BadProgramException("Branch node must have a computation of boolean type");
}
return Act;
}
private StatementAction ResolveStatement(ActionAttribute action) {
var stmt = new StatementAction(action);
switch (action) {
case OutputStringAttribute outs: break;
case OutputVariableAttribute outv:
stmt.Variable = ResolveVariable(outv.Variable);
CheckCanRead(stmt.Variable);
break;
case InputValueAttribute inv:
stmt.Variable = ResolveVariable(inv.Variable);
CheckCanWrite(stmt.Variable);
break;
case InvokeSubroutineAttribute invoke:
stmt.Subroutine = Root.ResolveName(invoke.TargetRoutine);
if (invoke.Args == null || invoke.Args.Length == 0) {
stmt.Arguments = Array.Empty<Variable>();
} else {
stmt.Arguments = new Variable[invoke.Args.Length];
int idx = 0;
foreach (var arg in invoke.Args) {
CheckCanRead(stmt.Arguments[idx] = ResolveVariable(arg));
++idx;
}
}
if (invoke.Result != null)
CheckCanWrite(stmt.ResultVariable = ResolveVariable(invoke.Result));
// Signature match check
if (stmt.Subroutine.ParameterSignature.Length != stmt.Arguments.Length)
throw new BadProgramException("Subroutine call argument count mismatch");
for (int idx = 0; idx != stmt.Arguments.Length; ++idx) {
if (!stmt.Arguments[idx].Type.IsAssignableTo(stmt.Subroutine.ParameterSignature[idx]))
throw new InvalidCastException("Cannot convert subroutine call argument to target type");
}
// TODO: Result type check (first find it out)
// Right now, it will be handled in codegen
break;
case AssignVariable assign:
stmt.Variable = ResolveVariable(assign.Variable);
CheckCanWrite(stmt.Variable);
if (assign.Value == null)
if (!stmt.Variable.Type.IsClass)
throw new InvalidCastException("Cannot assign a null value to a value type");
else if (!stmt.Variable.Type.Equals(assign.Value.GetType()))
throw new InvalidCastException($"Cannot assign a {assign.Value.GetType().FullName} value to a variable of type {stmt.Variable.GetType().FullName}");
break;
default:
throw new NotSupportedException("Action is not supported");
}
return stmt;
}
private ComputationScope ResolveExpression(ComputationAttribute comp, Type scope) {
var flow = (ComputeFlowAttribute)Attribute.GetCustomAttribute(scope, typeof(ComputeFlowAttribute));
if (!(flow is ComputeNextAttribute))
throw new BadProgramException("Computation must begin with a next label");
var expr = new ComputationScope(scope);
expr.Entry = ResolveComputation(expr, comp, scope);
return expr;
}
private ComputeAction ResolveComputation(ComputationScope scope, ComputationAttribute comp, MemberInfo member) {
var expr = new ComputeAction(comp, member);
StackBehaviorOptions stackBeh = ((StackBehaviorAttribute)Attribute.GetCustomAttribute(member, typeof(StackBehaviorAttribute)))?.Options ?? default;
expr.StackBehavior = stackBeh;
var flow = (ComputeFlowAttribute)Attribute.GetCustomAttribute(member, typeof(ComputeFlowAttribute));
if (flow == null)
throw new BadProgramException("Computation must have a flow attribute");
switch (comp) {
case LoadVariableAttribute load:
expr.Variable = ResolveVariable(load.Variable);
CheckCanRead(expr.Variable);
scope.PushValue(expr.Variable.Type);
break;
case LoadVariableAddressAttribute loada:
expr.Variable = ResolveVariable(loada.Variable);
CheckCanRead(expr.Variable);
// TODO: Puttis in codegen
// if ((expr.Variable.Flags & VariableFlags.Writable) != 0)
// ilgen.Emit(OpCodes.Readonly);
scope.PushValue(expr.Variable.Type.MakeByRefType());
break;
case LoadConstantAttribute constant:
scope.PushValue(constant.Type);
break;
case DereferenceValueAttribute deref: {
var type = scope.TopValue;
if (!type.IsByRef)
throw new InvalidCastException("Dereferenced value is not by-ref type");
if (!stackBeh.HasFlag(StackBehaviorOptions.NoPop))
scope.PopValue();
scope.PushValue(type.GetElementType());
break;
}
case StoreDerefValueAttribute store: {
var value = scope.PopValue();
var type = scope.PopValue();
if (!type.IsByRef)
throw new InvalidCastException("Store target is not by-ref type");
if (!value.IsAssignableTo(type.GetElementType()))
throw new InvalidCastException("Store value and target types don't match");
if (stackBeh.HasFlag(StackBehaviorOptions.NoPop)) {
scope.PushValue(type);
scope.PushValue(value);
}
break;
}
case CompareValuesAttribute:
case AddValuesAttribute:
case SubtractValuesAttribute:
case MultiplyValuesAttribute:
case DivideValuesAttribute:
case DivideModuleValuesAttribute: {
var rhs = scope.PopValue();
var lhs = scope.PopValue();
if (stackBeh.HasFlag(StackBehaviorOptions.NoPop)) {
scope.PushValue(lhs);
scope.PushValue(rhs);
}
if (!rhs.Equals(lhs))
throw new InvalidCastException($"Arithmetic operands' types don't match ({lhs.FullName} and {rhs.FullName})");
if (!rhs.IsPrimitive)
throw new InvalidCastException($"Arithmetic operands aren't primitive types ({lhs.FullName})");
if (comp is CompareValuesAttribute)
scope.PushValue(typeof(bool));
else
scope.PushValue(lhs);
break;
}
case ConvertToBooleanAttribute: {
var value = scope.PopValue();
scope.PushValue(typeof(bool));
break;
}
default:
throw new NotSupportedException("Computation is not supported");
}
L_execFlow:
if (flow is ComputeNextAttribute next) {
var nextMemb = scope.LookupNextMember(next.Symbol);
var nextComp = (ComputationAttribute)Attribute.GetCustomAttribute(nextMemb, typeof(ComputationAttribute));
if (nextComp == null) {
flow = (ComputeFlowAttribute)Attribute.GetCustomAttribute(nextMemb, typeof(ComputeFlowAttribute));
if (flow == null)
throw new BadProgramException("Computation must have a flow attribute");
goto L_execFlow;
//throw new BadProgramException("Entity is not a computation");
}
expr.Next = ResolveComputation(scope, nextComp, nextMemb);
} else if (flow is ComputeDoneAttribute) {
if (scope.Stack.Count != 0)
throw new BadProgramException("Expected empty stack");
} else if (flow is ComputeResultAttribute) {
if (scope.Stack.Count != 1)
throw new BadProgramException("Expected single value on stack");
scope.ResultType = scope.TopValue;
} else {
throw new NotSupportedException("Invalid compute flow");
}
return expr;
}
public void Run(object[] args=null) {
if (!BytecodeGenerated)
throw new InvalidOperationException("Method is not compiled yet");
Bytecode.Invoke(null, args);
}
private DynamicMethod Bytecode;
private bool BytecodeGenerated;
private MethodInfo GetMethodInfoForCall()
=> Bytecode ??= new DynamicMethod(Target.FullName, ResultType, ParameterSignature, Target);
public void GenerateBytecode() {
if (BytecodeGenerated)
return;
if (EntryPoint == null)
throw new InvalidOperationException("Method has no entry point");
Bytecode ??= new DynamicMethod(Target.FullName, ResultType, ParameterSignature, Target);
BytecodeGenerated = true;
var ilgen = Bytecode.GetILGenerator();
var visited = new HashSet<FlowAction>();
var stack = new Stack<FlowAction>();
stack.Push(EntryPoint);
foreach (var variable in ResolvedVariables.Values) {
if (variable.ParamIndex == 0)
variable.LocalRef = ilgen.DeclareLocal(variable.Type);
else
Bytecode.DefineParameter(variable.ParamIndex, ParameterAttributes.None, variable.Name);
}
foreach (var action in ResolvedActions.Values) {
action.Label = ilgen.DefineLabel();
}
while (stack.Count != 0) {
var act = stack.Pop();
if (visited.Contains(act))
continue;
visited.Add(act);
GenerateAction(act, ilgen, stack);
}
//Console.WriteLine("Done compiling method {0}", Bytecode);
//DumpMethodBody(Bytecode.GetMethodBody());
}
private MethodInfo cachedWriteLineString_;
private MethodInfo cachedWriteLineString
=> cachedWriteLineString_ ??= typeof(Console).GetMethod("WriteLine", new[] {typeof(string)});
private MethodInfo cachedWriteString_;
private MethodInfo cachedWriteString
=> cachedWriteString_ ??= typeof(Console).GetMethod("Write", new[] {typeof(string)});
private MethodInfo cachedWriteLineInt_;
private MethodInfo cachedWriteLineInt
=> cachedWriteLineInt_ ??= typeof(Console).GetMethod("WriteLine", new[] {typeof(int)});
private MethodInfo cachedWriteInt_;
private MethodInfo cachedWriteInt
=> cachedWriteInt_ ??= typeof(Console).GetMethod("Write", new[] {typeof(int)});
private MethodInfo cachedReadLine_;
private MethodInfo cachedReadLine
=> cachedReadLine_ ??= typeof(Console).GetMethod("ReadLine", Array.Empty<Type>());
private MethodInfo cachedRead_;
private MethodInfo cachedRead
=> cachedRead_ ??= typeof(Console).GetMethod("Read", Array.Empty<Type>());
private MethodInfo cachedTrim_;
private MethodInfo cachedTrim
=> cachedTrim_ ??= typeof(string).GetMethod("Trim", Array.Empty<Type>());
private Exception excNoPop => new NotSupportedException("No popping is currently not supported in this context");
private void GenerateAction(FlowAction action, ILGenerator ilgen, Stack<FlowAction> stack) {
ilgen.MarkLabel(action.Label);
if (action.Computation is ComputationScope compScope) {
var opstack = new Stack<OperandStackValueType>();
for (var comp = compScope.Entry; comp != null; comp = comp.Next)
switch (comp.Action) {
case LoadVariableAttribute:
EmitVariableAccess(ilgen, comp.Variable, VariableAccessKind.Load);
opstack.Push(TypeToOperandType(comp.Variable.Type));
break;
case LoadVariableAddressAttribute:
{
EmitVariableAccess(ilgen, comp.Variable, VariableAccessKind.Address);
/*int typ = (int)TypeToOperandType(comp.Action.Variable.Type);
if (typ >= (int)OperandStackValueType.I4 && typ <= (int)OperandStackValueType.U)
opstack.Push((OperandStackValueType)(typ + 8));
else
opstack.Push(OperandStackValueType.RefOther);*/
opstack.Push(TypeToRefOperandType(comp.Variable.Type));
break;
}
case LoadConstantAttribute cons:
GenerateNumericValue(ilgen, Convert.ChangeType(cons.Value, cons.Type), cons.Type);
opstack.Push(TypeToOperandType(cons.Type));
break;
case DereferenceValueAttribute:
{
if ((comp.StackBehavior & StackBehaviorOptions.NoPop) != 0)
{
ilgen.Emit(OpCodes.Dup);
opstack.Push(opstack.Peek());
}
var typ = opstack.Pop();
ilgen.Emit(typ switch {
OperandStackValueType.RefI1 => OpCodes.Ldind_I1,
OperandStackValueType.RefU1 => OpCodes.Ldind_U1,
OperandStackValueType.RefI2 => OpCodes.Ldind_I2,
OperandStackValueType.RefU2 => OpCodes.Ldind_U2,
OperandStackValueType.RefI4 => OpCodes.Ldind_I4,
OperandStackValueType.RefU4 => OpCodes.Ldind_U4,
OperandStackValueType.RefI8 => OpCodes.Ldind_I8,
OperandStackValueType.RefU8 => OpCodes.Ldind_I8,
OperandStackValueType.RefI => OpCodes.Ldind_I,
OperandStackValueType.RefU => OpCodes.Ldind_I,
OperandStackValueType.RefOther => throw new InvalidCastException("Unknown reference type for dereferencing"),
_ => throw new InvalidOperationException("Invalid dereference reached codegen"),
});
opstack.Push(typ switch {
OperandStackValueType.RefI1 or OperandStackValueType.RefI2 or OperandStackValueType.RefI4 => OperandStackValueType.I4,
OperandStackValueType.RefU1 or OperandStackValueType.RefU2 or OperandStackValueType.RefU4 => OperandStackValueType.U4,
OperandStackValueType.RefI8 => OperandStackValueType.I8,
OperandStackValueType.RefU8 => OperandStackValueType.U8,
OperandStackValueType.RefI => OperandStackValueType.I,
OperandStackValueType.RefU => OperandStackValueType.U,
_ => default,
});
break;
}
case StoreDerefValueAttribute:
if ((comp.StackBehavior & StackBehaviorOptions.NoPop) != 0)
throw excNoPop;
opstack.Pop();
ilgen.Emit(opstack.Pop() switch {
OperandStackValueType.RefI1 => OpCodes.Stind_I1,
OperandStackValueType.RefU1 => OpCodes.Stind_I1,
OperandStackValueType.RefI2 => OpCodes.Stind_I2,
OperandStackValueType.RefU2 => OpCodes.Stind_I2,
OperandStackValueType.RefI4 => OpCodes.Stind_I4,
OperandStackValueType.RefU4 => OpCodes.Stind_I4,
OperandStackValueType.RefI8 => OpCodes.Stind_I8,
OperandStackValueType.RefU8 => OpCodes.Stind_I8,
OperandStackValueType.RefI => OpCodes.Stind_I,
OperandStackValueType.RefU => OpCodes.Stind_I,
OperandStackValueType.RefOther => throw new InvalidCastException("Unknown reference type for dereferencing"),
_ => throw new InvalidOperationException("Invalid dereference reached codegen"),
});
break;
case CompareValuesAttribute cmp: {
var trhs = opstack.Pop();
var tlhs = opstack.Pop();
if (tlhs != trhs)
throw new InvalidOperationException("Mismatch between compared types");
bool isUnsigned = cmp.Comparison == Comparison.Equals || cmp.Comparison == Comparison.NotEquals
|| tlhs switch {
OperandStackValueType.I4 or OperandStackValueType.I8 or OperandStackValueType.I
or OperandStackValueType.Float => false,
OperandStackValueType.U4 or OperandStackValueType.U8 or OperandStackValueType.U
=> true,
_ => throw new InvalidOperationException("Cannot compare those values"),
};
opstack.Push(OperandStackValueType.I4);
if (comp.Next == null && action.AttrSeq is SeqFlowBranchAttribute) {
// Emit branch instruction
switch (cmp.Comparison) {
case Comparison.Equals:
ilgen.Emit(OpCodes.Beq_S, action.BranchTrue.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchFalse.Label);
break;
case Comparison.NotEquals:
ilgen.Emit(OpCodes.Beq_S, action.BranchFalse.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchTrue.Label);
break;
case Comparison.LessThan:
ilgen.Emit(isUnsigned ? OpCodes.Blt_Un_S : OpCodes.Blt_S, action.BranchTrue.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchFalse.Label);
break;
case Comparison.GreaterThan:
ilgen.Emit(isUnsigned ? OpCodes.Bgt_Un_S : OpCodes.Bgt_S, action.BranchTrue.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchFalse.Label);
break;
case Comparison.LessOrEqualTo:
ilgen.Emit(isUnsigned ? OpCodes.Ble_Un_S : OpCodes.Ble_S, action.BranchTrue.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchFalse.Label);
break;
case Comparison.GreaterOrEqualTo:
ilgen.Emit(isUnsigned ? OpCodes.Bge_Un_S : OpCodes.Bge_S, action.BranchTrue.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchFalse.Label);
break;
}
stack.Push(action.BranchFalse);
stack.Push(action.BranchTrue);
return;
}
switch (cmp.Comparison) {
case Comparison.Equals:
ilgen.Emit(OpCodes.Ceq);
break;
case Comparison.NotEquals:
ilgen.Emit(OpCodes.Ceq);
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Ceq);
break;
case Comparison.LessThan:
ilgen.Emit(isUnsigned ? OpCodes.Clt_Un : OpCodes.Clt);
break;
case Comparison.GreaterThan:
ilgen.Emit(isUnsigned ? OpCodes.Cgt_Un : OpCodes.Cgt);
break;
case Comparison.LessOrEqualTo:
ilgen.Emit(isUnsigned ? OpCodes.Cgt_Un : OpCodes.Cgt);
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Ceq);
break;
case Comparison.GreaterOrEqualTo:
ilgen.Emit(isUnsigned ? OpCodes.Clt_Un : OpCodes.Cgt);
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Ceq);
break;
}}
break;
case ConvertToBooleanAttribute: {
var typ = opstack.Pop();
ilgen.Emit(OpCodes.Ldc_I4_0);
if (typ == OperandStackValueType.I8 || typ == OperandStackValueType.U8)
ilgen.Emit(OpCodes.Conv_I8);
else if (typ == OperandStackValueType.I || typ == OperandStackValueType.U)
ilgen.Emit(OpCodes.Conv_I);
ilgen.Emit(OpCodes.Cgt_Un);
opstack.Push(OperandStackValueType.I4);
break;
}
case AddValuesAttribute:
case SubtractValuesAttribute:
case MultiplyValuesAttribute:
case DivideValuesAttribute:
case DivideModuleValuesAttribute: {
var trhs = opstack.Pop();
var tlhs = opstack.Pop();
if (tlhs != trhs)
throw new InvalidOperationException("Mismatch between compared types");
bool isUnsigned = tlhs switch {
OperandStackValueType.I4 or OperandStackValueType.I8 or OperandStackValueType.I
or OperandStackValueType.Float => false,
OperandStackValueType.U4 or OperandStackValueType.U8 or OperandStackValueType.U
=> true,
_ => throw new InvalidOperationException("Cannot do arithmetic on those types"),
};
switch (comp.Action) {
case AddValuesAttribute:
ilgen.Emit(isUnsigned ? OpCodes.Add_Ovf_Un : OpCodes.Add_Ovf);
break;
case SubtractValuesAttribute:
ilgen.Emit(isUnsigned ? OpCodes.Sub_Ovf_Un : OpCodes.Sub_Ovf);
break;
case MultiplyValuesAttribute:
ilgen.Emit(isUnsigned ? OpCodes.Mul_Ovf_Un : OpCodes.Mul_Ovf);
break;
case DivideValuesAttribute:
ilgen.Emit(isUnsigned ? OpCodes.Div_Un : OpCodes.Div);
break;
case DivideModuleValuesAttribute:
ilgen.Emit(isUnsigned ? OpCodes.Rem_Un : OpCodes.Rem);
break;
}
opstack.Push(tlhs);
break;
}
}
/*if (opstack.Count != 0)
Console.WriteLine("Debug: items on the stack = {0}", opstack.Count);*/
}
else if (action.Action is StatementAction stmt)
switch (stmt.Action) {
case OutputStringAttribute outs:
ilgen.Emit(OpCodes.Ldstr, outs.String);
ilgen.Emit(OpCodes.Call, cachedWriteString);
break;
case OutputVariableAttribute outv: {
var type = stmt.Variable.Type;
if (!new[] {typeof(string), typeof(char), typeof(bool), typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(sbyte), typeof(byte), typeof(long), typeof(ulong), typeof(nint), typeof(nuint)}.Contains(type))
throw new InvalidCastException($"Type {type.FullName} cannot be printed");
EmitVariableAccess(ilgen, stmt.Variable, VariableAccessKind.Load);
MethodInfo mi;
TypeCode tc = Type.GetTypeCode(type);
switch (tc)
{
case TypeCode.Int16:
case TypeCode.SByte:
ilgen.Emit(OpCodes.Conv_I4);
type = typeof(int);
break;
case TypeCode.UInt16:
case TypeCode.Byte:
ilgen.Emit(OpCodes.Conv_U4);
type = typeof(uint);
break;
/*case TypeCode.NativeInt:
ilgen.Emit(OpCodes.Conv_I8);
type = typeof(long);
break;
case TypeCode.NativeUInt:
ilgen.Emit(OpCodes.Conv_U8);
type = typeof(ulong);
break;*/
}
if (outv.Newline) {
if (type == typeof(int))
mi = cachedWriteLineInt;
else if (type == typeof(string))
mi = cachedWriteLineString;
else
mi = typeof(Console).GetMethod("WriteLine", new[] {type});
} else {
if (type == typeof(int))
mi = cachedWriteInt;
else if (type == typeof(string))
mi = cachedWriteString;
else
mi = typeof(Console).GetMethod("Write", new[] {type});
}
ilgen.Emit(OpCodes.Call, mi);
break;
}
case InputValueAttribute inv: {
var type = stmt.Variable.Type;
if (!new[] {typeof(string), typeof(char), typeof(bool), typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(sbyte), typeof(byte), typeof(long), typeof(ulong), typeof(nint), typeof(nuint)}.Contains(type))
throw new InvalidCastException($"Type {type.FullName} cannot be printed");
if (!string.IsNullOrEmpty(inv.Prompt)) {
ilgen.Emit(OpCodes.Ldstr, inv.Prompt);
ilgen.Emit(OpCodes.Call, cachedWriteString);
}
if (type == typeof(string)) {
ilgen.Emit(OpCodes.Call, cachedReadLine);
} else if (type == typeof(char)) {
ilgen.Emit(OpCodes.Call, cachedRead);
} else {
ilgen.Emit(OpCodes.Call, cachedReadLine);
ilgen.Emit(OpCodes.Call, cachedTrim);
ilgen.Emit(OpCodes.Call, type.GetMethod("Parse", new[] {typeof(string)}));
}
EmitVariableAccess(ilgen, stmt.Variable, VariableAccessKind.Store);
break;
}
case InvokeSubroutineAttribute invoke: {
// Crossref: check result type match here
if (stmt.ResultVariable != null && !stmt.ResultVariable.Type.IsAssignableFrom(stmt.Subroutine.ResultType))
throw new InvalidCastException("Cannot convert subroutine call result to variable type");
foreach (var arg in stmt.Arguments) {
EmitVariableAccess(ilgen, arg, VariableAccessKind.Load);
}
var sub = stmt.Subroutine.GetMethodInfoForCall();
ilgen.Emit(OpCodes.Call, sub);
if (stmt.ResultVariable != null) {
EmitVariableAccess(ilgen, stmt.ResultVariable, VariableAccessKind.Store);
} else if (stmt.Subroutine.ResultType != typeof(void)) {
ilgen.Emit(OpCodes.Pop);
}
break;
}
case AssignVariable assign:
if (assign.Value == null)
ilgen.Emit(OpCodes.Ldnull);
else {
var type = stmt.Variable.Type;
if (type == typeof(string))
ilgen.Emit(OpCodes.Ldstr, (string)assign.Value);
else if (type.IsPrimitive)
GenerateNumericValue(ilgen, assign.Value, type);
else
throw new InvalidProgramException("Only string and primitive types can be explicitly assigned");
}
EmitVariableAccess(ilgen, stmt.Variable, VariableAccessKind.Store);
break;
}
if (action.Computation?.ResultType != null && action.Computation.ResultType != typeof(void) && !(action.AttrSeq is SeqFlowBranchAttribute))
// Pop computed value
ilgen.Emit(OpCodes.Pop);
switch (action.AttrSeq) {
case SeqFlowNextAttribute:
ilgen.Emit(OpCodes.Br_S, action.Next.Label);
stack.Push(action.Next);
break;
case SeqFlowBranchAttribute:
ilgen.Emit(OpCodes.Brfalse_S, action.BranchFalse.Label);
ilgen.Emit(OpCodes.Br_S, action.BranchTrue.Label);
stack.Push(action.BranchFalse);
stack.Push(action.BranchTrue);
break;
case SeqFlowHaltAttribute halt:
GenerateNumericValue(ilgen, halt.Status, typeof(int));
ilgen.Emit(OpCodes.Call, typeof(Environment).GetMethod("Exit", new[] {typeof(int)}));
ilgen.Emit(OpCodes.Ret);
break;
case SubroutineResultAttribute:
if (action.ResultVar != null)
EmitVariableAccess(ilgen, action.ResultVar, VariableAccessKind.Load);
ilgen.Emit(OpCodes.Ret);
break;
}
}
private enum VariableAccessKind {
Load, Store, Address,
}
private void EmitVariableAccess(ILGenerator ilgen, Variable variable, VariableAccessKind access) {
if (variable.LocalRef != null) {
switch (access) {
case VariableAccessKind.Load:
ilgen.Emit(OpCodes.Ldloc_S, variable.LocalRef);
break;
case VariableAccessKind.Store:
ilgen.Emit(OpCodes.Stloc_S, variable.LocalRef);
break;
case VariableAccessKind.Address:
ilgen.Emit(OpCodes.Ldloca_S, variable.LocalRef);
break;
}
} else {
switch (access) {
case VariableAccessKind.Load:
ilgen.Emit(OpCodes.Ldarg_S, variable.ParamIndex - 1);
break;
case VariableAccessKind.Store:
ilgen.Emit(OpCodes.Starg_S, variable.ParamIndex - 1);
break;
case VariableAccessKind.Address:
ilgen.Emit(OpCodes.Ldarga_S, variable.ParamIndex - 1);
break;
}
}
}
private void GenerateNumericValue(ILGenerator ilgen, object value, Type type) {
ulong uns;
long sig;
switch (Type.GetTypeCode(type)) {
case TypeCode.Boolean:
ilgen.Emit((bool)value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
break;
case TypeCode.Char:
uns = (char)value;
goto emitU4;
case TypeCode.SByte:
sig = (sbyte)value;
goto emitI4;
case TypeCode.Byte:
uns = (byte)value;
goto emitU4;
case TypeCode.Int16:
sig = (short)value;
goto emitI4;
case TypeCode.UInt16:
uns = (ushort)value;
goto emitU4;
case TypeCode.Int32:
sig = (int)value;
goto emitI4;
case TypeCode.UInt32:
uns = (uint)value;
goto emitU4;
case TypeCode.Int64:
sig = (long)value;
goto emitI8;
case TypeCode.UInt64:
uns = (ulong)value;
goto emitU8;
case TypeCode.Single:
ilgen.Emit(OpCodes.Ldc_R4, (float)value);
break;
case TypeCode.Double:
ilgen.Emit(OpCodes.Ldc_R8, (double)value);
break;
default:
throw new InvalidOperationException("Only numeric types are supported");
}
return;
emitI4:
if (sig >= -1 && sig <= 8)
ilgen.Emit(NumeralOpCode((int)sig));
else if (sig >= -0x80 && sig <= 0x7F)
ilgen.Emit(OpCodes.Ldc_I4_S, (int)sig);
else
ilgen.Emit(OpCodes.Ldc_I4, sig);
return;
emitU4:
if (uns <= 8)
ilgen.Emit(NumeralOpCode((int)uns));
else if (uns <= 0x7F)
ilgen.Emit(OpCodes.Ldc_I4_S, (uint)uns);
else
ilgen.Emit(OpCodes.Ldc_I4, uns);
return;
emitI8:
if (sig >= -1 && sig <= 8)
ilgen.Emit(NumeralOpCode((int)sig));
else if (sig >= -0x80 && sig <= 0x7F)
ilgen.Emit(OpCodes.Ldc_I4_S, sig);
else if (sig >= -0x8000_0000L && sig <= 0x7FFF_FFFFL)
ilgen.Emit(OpCodes.Ldc_I4, sig);
else {
ilgen.Emit(OpCodes.Ldc_I8, sig);
return;
}
ilgen.Emit(OpCodes.Conv_I8);
return;
emitU8:
if (uns <= 8)
ilgen.Emit(NumeralOpCode((int)uns));
else if (uns <= 0x7F)
ilgen.Emit(OpCodes.Ldc_I4_S, uns);
else if (uns <= 0x7FFF_FFFFL)
ilgen.Emit(OpCodes.Ldc_I4, uns);
else {
ilgen.Emit(OpCodes.Ldc_I8, uns);
return;
}
ilgen.Emit(OpCodes.Conv_U8);
return;
}
// Computes OpCodes.Ldc_I4_0 + num
private static OpCode NumeralOpCode(int num) => num switch {
-1 => OpCodes.Ldc_I4_M1,
0 => OpCodes.Ldc_I4_0,
1 => OpCodes.Ldc_I4_1,
2 => OpCodes.Ldc_I4_2,
3 => OpCodes.Ldc_I4_3,
4 => OpCodes.Ldc_I4_4,
5 => OpCodes.Ldc_I4_5,
6 => OpCodes.Ldc_I4_6,
7 => OpCodes.Ldc_I4_7,
8 => OpCodes.Ldc_I4_8,
_ => throw null,
};
private static void CheckCanRead(Variable variable) {
if ((variable.Flags & VariableFlags.Readable) == 0)
throw new InvalidProgramException("Variable cannot be read");
}
private static void CheckCanWrite(Variable variable) {
if ((variable.Flags & VariableFlags.Writable) == 0)
throw new InvalidProgramException("Variable cannot be written");
}
private static void DumpMethodBody(MethodBody mb) {
if (mb.InitLocals)
Console.WriteLine("Locals are initialised");
Console.WriteLine("Max stack = {0}", mb.MaxStackSize);
Console.WriteLine("Local variables:");
foreach (var local in mb.LocalVariables) {
Console.WriteLine("\t{0}", local);
}
Console.WriteLine("Bytecode for the method: {0}", mb.GetILAsByteArray());
}
}
internal enum OperandStackValueType {
Unknown,
ValueType,
I4,
U4,
I8,
U8,
I,
U,
Float,
ObjRef,
// Difference between Nx types and RefNx types is 8
RefI1,
RefU1,
RefI2,
RefU2,
RefI4,
RefU4,
RefI8,
RefU8,
RefI,
RefU,
RefOther,
Ptr,
};
internal static class OperandStackValueTypeHelpers
{
public static OperandStackValueType TypeToOperandType(Type type) => TypeCodeToOperandType(Type.GetTypeCode(type));
public static OperandStackValueType TypeCodeToOperandType(TypeCode tc) => tc switch {
TypeCode.Empty or TypeCode.DBNull or TypeCode.String => OperandStackValueType.ObjRef,
TypeCode.Boolean or TypeCode.Char or TypeCode.Byte or TypeCode.UInt16 or TypeCode.UInt32 => OperandStackValueType.U4,
TypeCode.SByte or TypeCode.Int16 or TypeCode.Int32 => OperandStackValueType.I4,
TypeCode.UInt64 => OperandStackValueType.U8,
TypeCode.Int64 => OperandStackValueType.I8,
TypeCode.Single or TypeCode.Double => OperandStackValueType.Float,
TypeCode.DateTime => OperandStackValueType.ValueType,
_ => OperandStackValueType.Unknown,
};
public static OperandStackValueType TypeToRefOperandType(Type type) => TypeCodeToRefOperandType(Type.GetTypeCode(type));
public static OperandStackValueType TypeCodeToRefOperandType(TypeCode tc) => tc switch {
TypeCode.SByte => OperandStackValueType.RefI1,
TypeCode.Boolean or TypeCode.Byte => OperandStackValueType.RefU1,
TypeCode.Int16 => OperandStackValueType.RefI2,
TypeCode.Char or TypeCode.UInt16 => OperandStackValueType.RefU2,
TypeCode.Int32 => OperandStackValueType.RefI4,
TypeCode.UInt32 => OperandStackValueType.RefU4,
TypeCode.Int64 => OperandStackValueType.RefI8,
TypeCode.UInt64 => OperandStackValueType.RefU8,
//typeof(nint) => OperandStackValueType.RefI,
//typeof(nuint) => OperandStackValueType.RefU,
_ => OperandStackValueType.RefOther,
};
}
[Flags]
internal enum VariableFlags {
Readable = 1,
Writable = 2,
}
internal class Variable {
public string Name { get; }
public Type Type { get; }
public PropertyInfo Node { get; }
public VariableFlags Flags { get; }
public LocalBuilder LocalRef;
public int ParamIndex; // 1-based
public Variable(string Name, Type Type, PropertyInfo Node, VariableFlags Flags) {
this.Name = Name;
this.Type = Type;
this.Node = Node;
this.Flags = Flags;
}
}
internal class FlowAction {
public readonly MemberInfo Node;
public readonly SeqPointAttribute AttrSeq;
public readonly ActionAttribute AttrAction;
public FlowAction Next;
public FlowAction BranchTrue;
public FlowAction BranchFalse;
public Variable ResultVar;
public StatementAction Action;
public ComputationScope Computation;
public Label Label;
public FlowAction(MemberInfo Node, SeqPointAttribute SeqFlow, ActionAttribute Action) {
this.Node = Node;
this.AttrSeq = SeqFlow;
this.AttrAction = Action;
}
}
internal class StatementAction {
public readonly ActionAttribute Action;
public Variable Variable;
public Compiler Subroutine;
public Variable[] Arguments;
public Variable ResultVariable { get => Variable; set => Variable = value; }
public StatementAction(ActionAttribute Action) {
this.Action = Action;
}
}
internal class ComputationScope {
public readonly Type Scope;
public ComputeAction Entry;
private HashSet<string> VisitedMembers = new();
public readonly Stack<Type> Stack = new();
public Type ResultType;
public Type TopValue => Stack.Peek();
public void PushValue(Type value) => Stack.Push(value);
public Type PopValue() => Stack.Pop();
public ComputationScope(Type Scope) {
this.Scope = Scope;
}
public MemberInfo LookupNextMember(string Name) {
if (VisitedMembers.Contains(Name))
throw new BadProgramException("Cannot loop in computations");
VisitedMembers.Add(Name);
var memb = Scope.GetMember(Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (memb.Length != 1)
throw new BadProgramException("Expected one member of declared name");
if (memb[0].MemberType != MemberTypes.Property)
throw new BadProgramException("Compute node must be a property");
return memb[0];
}
}
internal class ComputeAction {
public readonly ComputationAttribute Action;
public readonly MemberInfo Node;
public StackBehaviorOptions StackBehavior;
public Variable Variable;
public ComputeAction Next;
public ComputeAction(ComputationAttribute Action, MemberInfo Node) {
this.Action = Action;
this.Node = Node;
}
}
}
#endregion VM Implementation
// Here is an example program
#region Example program
namespace MainProgram {
using AttrVM;
[EntryPoint(@"Start")]
[SubroutineArguments(new[] { @"MaxCount" })]
public static class Subroutine {
[AssignVariable(nameof(Iterator), (uint)0)]
[SeqFlowNext(nameof(RangeCheck))]
public static object Start { get; }
[LoadVariable(nameof(Iterator))]
[ComputeNext(@"LoadCount")]
[SeqFlowBranch(OnTrue= nameof(DoIteration), OnFalse= nameof(Exit))]
public static class RangeCheck {
[LoadVariable(nameof(MaxCount))]
[ComputeNext(nameof(Compare))]
public static object LoadCount { get; }
[CompareValues(Comparison.LessThan)]
[ComputeNext(nameof(Result))]
public static object Compare { get; }
[ComputeResult]
public static object Result { get; }
}
[LoadVariableAddress(nameof(Iterator))]
[ComputeNext(@"Deref")]
[SeqFlowNext(nameof(Case3Check))]
public static class DoIteration {
[DereferenceValue]
[StackBehavior(StackBehaviorOptions.NoPop)]
[ComputeNext(nameof(LoadOne))]
public static object Deref { get; }
[LoadConstant((uint)1)]
[ComputeNext(nameof(Add))]
public static object LoadOne { get; }
[AddValues]
[ComputeNext(nameof(Store))]
public static object Add { get; }
[StoreDerefValue]
[ComputeNext(nameof(Done))]
public static object Store { get; }
[ComputeDone]
public static object Done { get; }
}
[LoadVariable(nameof(Iterator))]
[ComputeNext(@"LoadConst")]
[SeqFlowBranch(OnTrue= nameof(Case1Check), OnFalse= nameof(DoCase3))]
public static class Case3Check {
[LoadConstant((uint)0x0F)]
[ComputeNext(nameof(AdvCompare))]
public static object LoadConst { get; }
[DivideModuleValues]
[ComputeNext(nameof(MakeResult))]
public static object AdvCompare { get; }
[ConvertToBoolean]
[ComputeResult]
public static object MakeResult { get; }
}
[OutputString("SneakSnack\n")]
[SeqFlowNext(nameof(RangeCheck))]
public static object DoCase3 { get; }
[LoadVariable(nameof(Iterator))]
[ComputeNext(@"LoadConst")]
[SeqFlowBranch(OnTrue= nameof(Case2Check), OnFalse= nameof(DoCase1))]
public static class Case1Check {
[LoadConstant((uint)0x03)]
[ComputeNext(nameof(AdvCompare))]
public static object LoadConst { get; }
[DivideModuleValues]
[ComputeNext(nameof(MakeResult))]
public static object AdvCompare { get; }
[ConvertToBoolean]
[ComputeResult]
public static object MakeResult { get; }
}
[OutputString("Sneak\n")]
[SeqFlowNext(nameof(RangeCheck))]
public static object DoCase1 { get; }
[LoadVariable(nameof(Iterator))]
[ComputeNext(@"LoadConst")]
[SeqFlowBranch(OnTrue= nameof(DoCase0), OnFalse= nameof(DoCase2))]
public static class Case2Check {
[LoadConstant((uint)0x05)]
[ComputeNext(nameof(AdvCompare))]
public static object LoadConst { get; }
[DivideModuleValues]
[ComputeNext(nameof(MakeResult))]
public static object AdvCompare { get; }
[ConvertToBoolean]
[ComputeResult]
public static object MakeResult { get; }
}
[OutputString("Snack\n")]
[SeqFlowNext(nameof(RangeCheck))]
public static object DoCase2 { get; }
[OutputVariable(nameof(Iterator), Newline= true)]
[SeqFlowNext(nameof(RangeCheck))]
public static object DoCase0 { get; }
[SubroutineResult]
public static object Exit { get; }
public static uint MaxCount { get; set; }
public static uint Iterator { get; set; }
}
[EntryPoint(@"Start")]
public static class Program {
[InputValue(nameof(MaxCount), Prompt= "Input number of iterations: ")]
[SeqFlowNext(nameof(RunComputation))]
public static object Start { get; }
[InvokeSubroutine(@"MainProgram.Subroutine", Args= new[] { nameof(MaxCount) }, Result= null)]
[SeqFlowNext(nameof(Halt))]
public static object RunComputation { get; }
[SeqFlowHalt(Status= 0)]
public static object Halt { get; }
public static uint MaxCount { get; set; }
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment