using AtLangCompiler.ILEmitter; using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; namespace AtLangCompiler.Methods; /// /// Token configuration for exit statements. /// internal class ExitToken : ILexerTokenConfig { /// /// Gets the mapping of token strings to values. /// public IReadOnlyDictionary TokenStrings => new Dictionary { { "@exit", TokenType.EXIT } }; } /// /// Represents an exit code expression in the abstract syntax tree. /// internal class ExitCodeStatement : ASTNode { /// /// Gets the expression that, when evaluated, produces the exit code string. /// public ASTNode Expr { get; } /// /// Initializes a new instance of the class. /// /// The expression to evaluate for the exit code string. public ExitCodeStatement(ASTNode expr) => Expr = expr; } /// /// Parser for exit code statements. /// [ParserFor(TokenType.EXIT, inputTokens: 1)] internal class ExitCodeStatementParser : IStatementParser { /// /// Parses an exit code statement. /// /// The parser instance. /// An representing the exit code statement. public ASTNode ParseStatement(Parser parser) { parser.Eat(TokenType.EXIT); parser.Eat(TokenType.LPAREN); ASTNode expr = parser.ParseExpression(); parser.Eat(TokenType.RPAREN); return new ExitCodeStatement(expr); } } /// /// IL emitter for exit code statements. /// [EmitterFor(typeof(ExitCodeStatement))] internal class Exit : IMethodEmitter { private readonly ILGenerator il; private readonly LocalBuilder dictLocal; private readonly ILMethodEmitterManager emitter; /// /// Initializes a new instance of the class. /// /// The instance used for emitting IL code. /// The local variable holding the runtime dictionary. /// The used to emit sub-expressions. public Exit(ILGenerator il, LocalBuilder dictLocal, ILMethodEmitterManager emitter) { this.il = il ?? throw new ArgumentNullException(nameof(il)); this.dictLocal = dictLocal ?? throw new ArgumentNullException(nameof(dictLocal)); this.emitter = emitter ?? throw new ArgumentNullException(nameof(emitter)); } /// /// Emits IL code to retrieve the exit code string from the runtime context, /// parse it into an integer, clamp the value between 0 and 255, and return it. /// If parsing fails, returns 255. /// /// /// The node containing the expression that provides the exit code string. /// /// /// The emitted IL expects that will push a /// onto the evaluation stack. /// public void EmitIL(ExitCodeStatement node) { const int minExit = 0; const int maxExit = 255; // Clamp between 0 and 255 for POSIX systems. const int failExit = 255; // Declare a local to hold the parsed integer. LocalBuilder parsedInt = il.DeclareLocal(typeof(int)); // Emit IL for the expression that pushes the exit code string onto the stack. emitter.EmitStatement(node.Expr); // Call int.TryParse(string, out int) il.Emit(OpCodes.Ldloca_S, parsedInt); MethodInfo tryParseMethod = typeof(int).GetMethod("TryParse", new[] { typeof(string), typeof(int).MakeByRefType() })!; il.Emit(OpCodes.Call, tryParseMethod); // Branch based on whether parsing succeeded. Label parseSuccess = il.DefineLabel(); Label endLabel = il.DefineLabel(); il.Emit(OpCodes.Brtrue_S, parseSuccess); // If parsing failed, load the failExit constant. il.Emit(OpCodes.Ldc_I4, failExit); il.Emit(OpCodes.Br_S, endLabel); // If parsing succeeded, clamp the value between minExit and maxExit. il.MarkLabel(parseSuccess); il.Emit(OpCodes.Ldloc, parsedInt); il.Emit(OpCodes.Ldc_I4, minExit); MethodInfo maxMethod = typeof(Math).GetMethod("Max", new[] { typeof(int), typeof(int) })!; il.Emit(OpCodes.Call, maxMethod); il.Emit(OpCodes.Ldc_I4, maxExit); MethodInfo minMethod = typeof(Math).GetMethod("Min", new[] { typeof(int), typeof(int) })!; il.Emit(OpCodes.Call, minMethod); il.MarkLabel(endLabel); il.Emit(OpCodes.Ret); } }