private bool IsScriptReference(string expectedReturnType, int expectedParameterCount, HS_Gen1Parser.CallContext context) { string key = context.callID().GetTextSanitized(); if(!_scriptLookup.ContainsKey(key)) { if (expectedReturnType == TypeHelper.ScriptReference) { throw new CompilerException($"The compiler expected a Script Reference but a Script with the name \"{key}\" could not be found. " + "Please check your script declarations and your spelling.", context); } else { return false; } } // Try to find a script, which satisfies all conditions. foreach(ScriptInfo info in _scriptLookup[key]) { if(info.Parameters.Count != expectedParameterCount) { continue; } string returnType = DetermineReturnType(info, expectedReturnType, context); if(!(returnType is null)) { // Check for equality functions. EqualityPush(returnType); // Push the parameters to the type stack. if (expectedParameterCount > 0) { string[] parameterTypes = info.Parameters.Select(p => p.ReturnType).ToArray(); _expectedTypes.PushTypes(parameterTypes); } // Create a script reference node. CreateScriptReference(info, _opcodes.GetTypeInfo(returnType).Opcode, context.GetCorrectTextPosition(_missingCarriageReturnPositions), GetLineNumber(context)); return true; } } return false; }
/// <summary> /// Processes function calls and script references. Links to a datum. Opens one or more datums. /// Pops a value type. Pushes parameter types. /// </summary> /// <param name="context"></param> public override void EnterCall(HS_Gen1Parser.CallContext context) { if (_debug) { _logger.Call(context, CompilerContextAction.Enter); } LinkDatum(); // Retrieve information from the context. string name = context.callID().GetTextSanitized(); string expectedType = _expectedTypes.PopType(); int contextParameterCount = context.expression().Length; // Handle script references. if (IsScriptReference(expectedType, contextParameterCount, context)) { return; } // Handle functions. var functions = _opcodes.GetFunctionInfo(name); foreach(var func in functions) { if(!CheckParameterSanity(context, func)) { continue; } string returnType = DetermineReturnType(func, expectedType, context); // If a function, which satisfies the requirements, was found, perform the necessary push operations and create expression nodes. if (!(returnType is null)) { EqualityPush(func.ReturnType); PushCallParameters(func, contextParameterCount, expectedType, context); CreateFunctionCall(func, _opcodes.GetTypeInfo(returnType).Opcode, context.GetCorrectTextPosition(_missingCarriageReturnPositions), GetLineNumber(context)); return; } } string errorMessage = contextParameterCount == 1 ? $"Failed to process the call {name} with 1 parameter." : $"Failed to process the call {name} with {contextParameterCount} parameters."; throw new CompilerException(errorMessage + $" The expected return type was {expectedType}.", context); }
public CompilerException(string message, HS_Gen1Parser.CallContext context) : base(message) { SetCommonProperties(context); Text = context.callID().GetTextSanitized(); }
public void Call(HS_Gen1Parser.CallContext context, CompilerContextAction action) { string name = context.callID().GetTextSanitized(); WriteContextIndent("CALL", context, action, name); }
/// <summary> /// Calculates the value types for the parameters of a function and pushes them to the stack. /// </summary> /// <param name="info">The function's ScriptFunctionInfo</param> /// <param name="context">The call contect</param> /// <param name="actualParameterCount">The number of parameters which was extracted from the context</param> private void PushCallParameters(FunctionInfo info, int contextParameterCount, string expectedReturnType, HS_Gen1Parser.CallContext context) { int expectedParamCount = info.ParameterTypes.Count(); // Handle regular functions. if (expectedParamCount > 0) { if (contextParameterCount != expectedParamCount) { throw new CompilerException($"The function \"{context.callID().GetTextSanitized()}\" has an unexpected number of arguments. Expected: \"{expectedParamCount}\" Encountered: \"{contextParameterCount}\".", context); } _expectedTypes.PushTypes(info.ParameterTypes); } // TODO: Throw exceptions if a wrong number of parameters is detected. // Handle special functions. #region special functions else { bool validNumberOfArgs = false; switch (info.Group) { // Any number of arguments. case "Begin": validNumberOfArgs = true; if (info.Name.Contains("random")) { _expectedTypes.PushTypes(expectedReturnType, contextParameterCount); } else { // the last evaluated expression. _expectedTypes.PushType(expectedReturnType); _expectedTypes.PushTypes("void", contextParameterCount - 1); } break; // Any number of arguments. case "BeginCount": validNumberOfArgs = true; _expectedTypes.PushTypes(expectedReturnType, contextParameterCount - 1); _expectedTypes.PushType("long"); break; // 2 or 3 arguments? case "If": validNumberOfArgs = contextParameterCount == 2 || contextParameterCount == 3; if(expectedReturnType == TypeHelper.Any) { _expectedTypes.PushTypes("void", contextParameterCount - 1); } else { _expectedTypes.PushTypes(expectedReturnType, contextParameterCount - 1); } _expectedTypes.PushType("boolean"); break; // Cond has it's own parser rule and should be handled elsewhere. case "Cond": throw new CompilerException("A cond call was not recognized by the parser.", context); // Two arguments. case "Set": validNumberOfArgs = contextParameterCount == 2; _expectedTypes.PushType(TypeHelper.GlobalsReference); // The second parameter will be pushed once we have determined the return type of the global. _set = true; break; // Any number of arguments. case "Logical": validNumberOfArgs = contextParameterCount >= 1; _expectedTypes.PushTypes("boolean", contextParameterCount); break; // Depends on the function. Some accept only two arguments. case "Arithmetic": validNumberOfArgs = contextParameterCount >= 1; _expectedTypes.PushTypes("real", contextParameterCount); break; // TODO: Change inequality to only accept NUMBER or maybe real arguments? // Two arguments. case "Equality": case "Inequality": validNumberOfArgs = contextParameterCount == 2; _expectedTypes.PushType("ANY"); _equality = true; break; // One or two arguments. case "Sleep": validNumberOfArgs = contextParameterCount == 1 || contextParameterCount == 2; if (contextParameterCount == 2) { _expectedTypes.PushType("script"); } _expectedTypes.PushType("short"); break; // Zero or one argument(s). case "SleepForever": validNumberOfArgs = contextParameterCount == 0 || contextParameterCount == 1; if (contextParameterCount == 1) { _expectedTypes.PushType("script"); } break; // One, two or three arguments. case "SleepUntil": validNumberOfArgs = contextParameterCount >= 1 || contextParameterCount <= 3; if (contextParameterCount == 3) { _expectedTypes.PushTypes("short", "long"); } else if(contextParameterCount == 2) { _expectedTypes.PushType("short"); } _expectedTypes.PushType("boolean"); break; // Probably two arguments. case "SleepUntilGameTicks": validNumberOfArgs = contextParameterCount == 2; if (contextParameterCount != 2) { throw new CompilerException("The Compiler encountered a \"sleep_until_game_ticks\" call, which didn't have exactly two arguments.", context); } _expectedTypes.PushTypes("boolean", "short"); break; // Probably one argument. case "CinematicSleep": validNumberOfArgs = contextParameterCount == 1; if (contextParameterCount != 1) { throw new CompilerException("The Compiler encountered a \"sleep_until_game_ticks\" call, which didn't have exactly one argument.", context); } _expectedTypes.PushType("short"); break; // One argument. case "Wake": validNumberOfArgs = contextParameterCount == 1; _expectedTypes.PushType("script"); break; // One argument. case "Inspect": validNumberOfArgs = contextParameterCount == 1; _expectedTypes.PushType("ANY"); break; // Branch has it's own parser rule and should be handled elsewhere. case "Branch": throw new CompilerException("A branch call was not identified by the parser.", context); // One argument. case "ObjectCast": validNumberOfArgs = contextParameterCount == 1; _expectedTypes.PushType("object"); break; // What is this? case null: return; default: throw new CompilerException($"Unimplemented function group: \"{info.Group}\".", context); } if(!validNumberOfArgs) { throw new CompilerException($"The special function \"{info.Name}\" has an invalid number of arguments: \"{contextParameterCount}\".", context); } } #endregion }