internal override Expression ResolveEntityNames( ParserContext parser) { FunctionDefinition funcDef; // used in multiple places. FieldDefinition fieldDec; this.Root = this.Root.ResolveEntityNames(parser); Expression root = this.Root; string field = this.FieldToken.Value; if (root is NamespaceReference) { // already a fully qualified namespace, therefore imports don't matter. string fullyQualifiedName = ((NamespaceReference)root).Template.Name + "." + field; TopLevelEntity entity = this.Owner.FileScope.FileScopeEntityLookup.DoEntityLookup(fullyQualifiedName, parser.CurrentCodeContainer); if (entity != null) { return(ResolverPipeline.ConvertStaticReferenceToExpression(entity, this.FirstToken, this.Owner)); } NamespaceReferenceTemplate nrt = this.Owner.FileScope.FileScopeEntityLookup.DoNamespaceLookup(fullyQualifiedName, parser.CurrentCodeContainer); if (nrt != null) { return(new NamespaceReference(this.FirstToken, this.Owner, nrt)); } throw new ParserException(this, "Could not find class or function by the name of: '" + fullyQualifiedName + "'"); } if (root is ClassReference) { ClassDefinition cd = ((ClassReference)root).ClassDefinition; funcDef = cd.GetMethod(field, false); if (funcDef != null) { if (!funcDef.Modifiers.HasStatic) { string className = cd.NameToken.Value; string functionName = funcDef.NameToken.Value; throw new ParserException(this.DotToken, "'" + className + "." + functionName + "' is not a static method, but it is being used as though it is static."); } Node.EnsureAccessIsAllowed(this.FieldToken, this.Owner, funcDef); return(new FunctionReference(this.FirstToken, funcDef, this.Owner)); } fieldDec = cd.GetField(field, false); if (fieldDec != null) { if (!fieldDec.Modifiers.HasStatic) { throw new ParserException(this.DotToken, "Cannot make a static reference to a non-static field."); } Node.EnsureAccessIsAllowed(this.FieldToken, this.Owner, fieldDec); return(new FieldReference(this.FirstToken, fieldDec, this.Owner)); } // TODO: typeof(class name) is less error prone with localization conflicts. // However there's a Core.typeOf() method that sort of conflicts. // TODO: if this notation is kept, then this needs to be split into two class keywords // since they do different things. if (field == parser.Keywords.CLASS) { return(new ClassReferenceLiteral(this.FirstToken, cd, this.Owner)); } // TODO: nested classes, enums, constants // TODO: show spelling suggestions. throw new ParserException(this.FieldToken, "No static fields or methods named '" + field + "' on the class " + cd.NameToken.Value + "."); } if (root is BaseKeyword) { ClassDefinition thisClass = null; if (this.Owner != null) { if (this.Owner is FunctionDefinition) { thisClass = this.Owner.Owner as ClassDefinition; } else { thisClass = this.Owner as ClassDefinition; } } if (thisClass == null) { throw new ParserException(root, "'base' keyword can only be used inside classes."); } ClassDefinition cd = thisClass.BaseClass; if (cd == null) { throw new ParserException(root, "'base' keyword can only be used inside classes that extend from another class."); } FunctionDefinition fd = cd.GetMethod(field, true); if (fd == null) { throw new ParserException(this.DotToken, "Cannot find a method by that name in the base class chain."); } if (fd.Modifiers.HasStatic) { throw new ParserException(this.DotToken, "Cannot reference static methods using 'base' keyword."); } Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, fd); return(new BaseMethodReference(this.FirstToken, this.DotToken, this.FieldToken, this.Owner)); } if (root is ThisKeyword) { ClassDefinition cd = null; TopLevelEntity owner = this.TopLevelEntity; if (owner is FunctionDefinition) { FunctionDefinition functionOwner = (FunctionDefinition)owner; if (functionOwner.Modifiers.HasStatic) { throw new ParserException(this.Root, "'this' keyword cannot be used in static methods."); } if (!(functionOwner.Owner is ClassDefinition)) { throw new ParserException(this.Root, "'this' keyword cannot be used in a function that isn't in a class."); } cd = (ClassDefinition)owner.Owner; } else if (owner is FieldDefinition) { if (((FieldDefinition)owner).Modifiers.HasStatic) { throw new ParserException(this.Root, "'this' keyword cannot be used in static fields."); } cd = (ClassDefinition)owner.Owner; } else if (owner is ConstructorDefinition) { if (((ConstructorDefinition)owner).Modifiers.HasStatic) { throw new ParserException(this.Root, "'this', keyword cannot be used in static constructors."); } cd = (ClassDefinition)owner.Owner; } else { throw new ParserException(this.Root, "'this' keyword must be used inside a class."); } funcDef = cd.GetMethod(field, true); if (funcDef != null) { if (funcDef.Modifiers.HasStatic) { throw new ParserException(this.DotToken, "This method is static and must be referenced by the class name, not 'this'."); } Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, funcDef); return(new FunctionReference(this.FirstToken, funcDef, this.Owner)); } FieldDefinition fieldDef = cd.GetField(field, true); if (fieldDef != null) { if (fieldDef.Modifiers.HasStatic) { throw new ParserException(this.DotToken, "This field is static and must be referenced by the class name, not 'this'."); } Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, fieldDef); return(new FieldReference(this.FirstToken, fieldDef, this.Owner)); } // TODO: show suggestions in the error message for anything close to what was typed. throw new ParserException(this.FieldToken, "The class '" + cd.NameToken.Value + "' does not have a field named '" + field + "'."); } if (this.Root is EnumReference) { EnumDefinition enumDef = ((EnumReference)this.Root).EnumDefinition; if (field == parser.Keywords.FIELD_ENUM_LENGTH) { return(new IntegerConstant(this.FirstToken, enumDef.Items.Length, this.Owner)); } if (field == parser.Keywords.FIELD_ENUM_MAX) { return(new SpecialEntity.EnumMaxFunction(this.FirstToken, enumDef, this.Owner)); } if (field == parser.Keywords.FIELD_ENUM_VALUES) { return(new SpecialEntity.EnumValuesFunction(this.FirstToken, enumDef, this.Owner)); } return(new EnumFieldReference(this.FirstToken, enumDef, this.FieldToken, this.Owner)); } // This is done here in the resolver instead of the parser because some unallowed // field names (such as .class) are valid. if (field == parser.Keywords.CLASS) { if (this.Root is Variable) { throw new ParserException(this.Root, "'" + ((Variable)this.Root).Name + "' is not a class."); } throw new ParserException(this.DotToken, ".class can only be applied to class names."); } parser.VerifyIdentifier(this.FieldToken); return(this); }
internal override Expression ResolveNames(ParserContext parser) { if (this.Name == "$$$") { throw new ParserException(this.FirstToken, "Core function invocations cannot stand alone and must be immediately invoked."); } if (this.Name.StartsWith("$$")) { return(new LibraryFunctionReference(this.FirstToken, this.Name.Substring(2), this.Owner)); } if (this.Name == "this" || this.Name == "base") { TopLevelConstruct container = parser.CurrentCodeContainer; if (container is FunctionDefinition) { FunctionDefinition funcDef = (FunctionDefinition)this.Owner; if (funcDef.IsStaticMethod) { throw new ParserException(this.FirstToken, "Cannot use '" + this.Name + "' in a static method"); } if (funcDef.Owner == null) { throw new ParserException(this.FirstToken, "Cannot use '" + this.Name + "' in a function that isn't a class method."); } } if (container is FieldDeclaration) { if (((FieldDeclaration)container).IsStaticField) { throw new ParserException(this.FirstToken, "Cannot use '" + this.Name + "' in a static field value."); } } if (container is ConstructorDefinition) { ConstructorDefinition constructor = (ConstructorDefinition)container; if (constructor == ((ClassDefinition)constructor.Owner).StaticConstructor) // TODO: This check is silly. Add an IsStatic field to ConstructorDefinition. { throw new ParserException(this.FirstToken, "Cannot use '" + this.Name + "' in a static constructor."); } } if (this.Name == "this") { return(new ThisKeyword(this.FirstToken, this.Owner)); } return(new BaseKeyword(this.FirstToken, this.Owner)); } NamespaceReferenceTemplate nrt = this.Owner.FileScope.FileScopeEntityLookup.DoNamespaceLookup(this.Name, this.Owner); if (nrt != null) { return(new NamespaceReference(this.FirstToken, this.Owner, nrt)); } TopLevelConstruct exec = this.Owner.FileScope.FileScopeEntityLookup.DoEntityLookup(this.Name, this.Owner); if (exec != null) { return(Resolver.ResolverPipeline.ConvertStaticReferenceToExpression(exec, this.FirstToken, this.Owner)); } return(this); }
internal override Expression ResolveTypes(ParserContext parser, TypeResolver typeResolver) { this.Root.ResolveTypes(parser, typeResolver); string field = this.FieldToken.Value; if (this.Root is EnumReference) { throw new System.NotImplementedException(); } ResolvedType rootType = this.Root.ResolvedType; // TODO: all of this needs to be localized. switch (rootType.Category) { case ResolvedTypeCategory.NULL: throw new ParserException(this.DotToken, "Cannot dereference a field from null."); case ResolvedTypeCategory.ANY: // ¯\_(ツ)_/¯ this.ResolvedType = ResolvedType.ANY; return(this); case ResolvedTypeCategory.INTEGER: throw new ParserException(this.DotToken, "Integers do not have any fields."); case ResolvedTypeCategory.FLOAT: throw new ParserException(this.DotToken, "Floating point decimals do not have any fields."); case ResolvedTypeCategory.BOOLEAN: throw new ParserException(this.DotToken, "Booleans do not have any fields."); case ResolvedTypeCategory.STRING: switch (field) { case "length": this.ResolvedType = ResolvedType.INTEGER; if (this.Root is StringConstant) { return(new IntegerConstant(this.FirstToken, ((StringConstant)this.Root).Value.Length, this.Owner)); } return(this); case "contains": return(BuildPrimitiveMethod(ResolvedType.BOOLEAN, ResolvedType.STRING)); case "endsWith": return(BuildPrimitiveMethod(ResolvedType.BOOLEAN, ResolvedType.STRING)); case "indexOf": return(BuildPrimitiveMethod(ResolvedType.INTEGER, ResolvedType.STRING)); case "lower": return(BuildPrimitiveMethod(ResolvedType.STRING)); case "ltrim": return(BuildPrimitiveMethod(ResolvedType.STRING)); case "replace": return(BuildPrimitiveMethod(ResolvedType.STRING, ResolvedType.STRING, ResolvedType.STRING)); case "reverse": return(BuildPrimitiveMethod(ResolvedType.STRING)); case "rtrim": return(BuildPrimitiveMethod(ResolvedType.STRING)); case "split": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(ResolvedType.STRING), ResolvedType.STRING)); case "startsWith": return(BuildPrimitiveMethod(ResolvedType.BOOLEAN, ResolvedType.STRING)); case "trim": return(BuildPrimitiveMethod(ResolvedType.STRING)); case "upper": return(BuildPrimitiveMethod(ResolvedType.STRING)); // common mistakes case "join": throw new ParserException(this.DotToken, "Strings do not have a .join(list) method. Did you mean to do list.join(string)?"); case "size": throw new ParserException(this.DotToken, "Strings do not have a .size() method. Did you mean to use .length?"); default: throw new ParserException(this.DotToken, "Strings do not have that method."); } case ResolvedTypeCategory.LIST: ResolvedType itemType = rootType.ListItemType; switch (field) { case "length": this.ResolvedType = ResolvedType.INTEGER; return(this); case "filter": case "map": if (this.CompilationScope.IsStaticallyTyped) { // TODO: for Acrylic, this needs to have a way to indicate that resolution should be attempted // again once the function return type is known. throw new System.NotImplementedException(); } this.ResolvedType = ResolvedType.ANY; return(this); case "add": return(BuildPrimitiveMethod(ResolvedType.VOID, itemType)); case "choice": return(BuildPrimitiveMethod(ResolvedType.VOID)); case "clear": return(BuildPrimitiveMethod(ResolvedType.VOID)); case "clone": return(BuildPrimitiveMethod(rootType)); case "concat": return(BuildPrimitiveMethod(rootType, rootType)); case "contains": return(BuildPrimitiveMethod(ResolvedType.BOOLEAN, itemType)); case "insert": return(BuildPrimitiveMethod(ResolvedType.VOID, ResolvedType.INTEGER, itemType)); case "join": return(BuildPrimitiveMethodWithOptionalArgs(ResolvedType.STRING, 1, ResolvedType.STRING)); case "pop": return(BuildPrimitiveMethod(itemType)); case "remove": return(BuildPrimitiveMethod(ResolvedType.VOID, ResolvedType.INTEGER)); case "reverse": return(BuildPrimitiveMethod(ResolvedType.VOID)); case "shuffle": return(BuildPrimitiveMethod(ResolvedType.VOID)); case "sort": return(BuildPrimitiveMethod(ResolvedType.VOID)); // common mistakes case "count": case "size": throw new ParserException(this.DotToken, "Lists do not have a ." + this.FieldToken.Value + "() method. Did you mean to use .length?"); default: throw new ParserException(this.DotToken, "Lists do not have that method."); } case ResolvedTypeCategory.DICTIONARY: ResolvedType keyType = rootType.DictionaryKeyType; ResolvedType valueType = rootType.DictionaryValueType; switch (field) { case "length": this.ResolvedType = ResolvedType.INTEGER; return(this); case "clear": return(BuildPrimitiveMethod(ResolvedType.VOID)); case "clone": return(BuildPrimitiveMethod(rootType)); case "contains": return(BuildPrimitiveMethod(ResolvedType.BOOLEAN, keyType)); case "get": return(BuildPrimitiveMethodWithOptionalArgs(valueType, 1, keyType, valueType)); case "keys": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(keyType))); case "merge": return(BuildPrimitiveMethod(ResolvedType.VOID, rootType)); case "remove": return(BuildPrimitiveMethod(ResolvedType.VOID, keyType)); case "values": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(valueType))); default: throw new ParserException(this.DotToken, "Dictionaries do not have that field."); } case ResolvedTypeCategory.CLASS_DEFINITION: throw new ParserException(this.DotToken, "Class definitions do not have that field."); case ResolvedTypeCategory.FUNCTION_POINTER: switch (field) { case "invoke": return(BuildPrimitiveMethod(ResolvedType.ANY, ResolvedType.ListOrArrayOf(ResolvedType.ANY))); default: throw new ParserException(this.DotToken, "Fucntions do not have that field."); } case ResolvedTypeCategory.INSTANCE: FieldDefinition fieldDef = rootType.ClassTypeOrReference.GetField(field, true); if (fieldDef != null) { if (!Node.IsAccessAllowed(this, fieldDef)) { ClassDefinition cd = fieldDef.ClassOwner; throw new ParserException(FieldToken, "The field '" + cd.NameToken.Value + "." + this.FieldToken.Value + "' is not accessible from here due to its access scope."); } this.ResolvedType = fieldDef.ResolvedFieldType; return(this); } FunctionDefinition funcDef = rootType.ClassTypeOrReference.GetMethod(field, true); if (funcDef != null) { this.ResolvedType = ResolvedType.GetFunctionType(funcDef); return(this); } throw new ParserException(this.DotToken, "The class '" + rootType.ClassTypeOrReference.NameToken.Value + "' does not have a field called '" + field + "'."); default: throw new ParserException(this.DotToken, "This field does not exist."); } }
internal override Expression ResolveNames( ParserContext parser) { FunctionDefinition funcDef; // used in multiple places. FieldDeclaration fieldDec; this.Root = this.Root.ResolveNames(parser); Expression root = this.Root; string field = this.StepToken.Value; if (root is NamespaceReference) { // already a fully qualified namespace, therefore imports don't matter. string fullyQualifiedName = ((NamespaceReference)root).Template.Name + "." + field; TopLevelConstruct entity = this.Owner.FileScope.FileScopeEntityLookup.DoEntityLookup(fullyQualifiedName, parser.CurrentCodeContainer); if (entity != null) { return(ResolverPipeline.ConvertStaticReferenceToExpression(entity, this.FirstToken, this.Owner)); } NamespaceReferenceTemplate nrt = this.Owner.FileScope.FileScopeEntityLookup.DoNamespaceLookup(fullyQualifiedName, parser.CurrentCodeContainer); if (nrt != null) { return(new NamespaceReference(this.FirstToken, this.Owner, nrt)); } throw new ParserException(this.FirstToken, "Could not find class or function by the name of: '" + fullyQualifiedName + "'"); } if (root is ClassReference) { ClassDefinition cd = ((ClassReference)root).ClassDefinition; funcDef = cd.GetMethod(field, false); if (funcDef != null) { if (!funcDef.IsStaticMethod) { string className = cd.NameToken.Value; string functionName = funcDef.NameToken.Value; throw new ParserException(this.DotToken, "'" + className + "." + functionName + "' is not a static method, but it is being used as though it is static."); } return(new FunctionReference(this.FirstToken, funcDef, this.Owner)); } fieldDec = cd.GetField(field, false); if (fieldDec != null) { if (!fieldDec.IsStaticField) { throw new ParserException(this.DotToken, "Cannot make a static reference to a non-static field."); } return(new FieldReference(this.FirstToken, fieldDec, this.Owner)); } if (field == "class") { return(new ClassReferenceLiteral(this.FirstToken, cd, this.Owner)); } // TODO: nested classes, enums, constants // TODO: show spelling suggestions. throw new ParserException(this.StepToken, "No static fields or methods named '" + field + "' on the class " + cd.NameToken.Value + "."); } if (root is BaseKeyword) { ClassDefinition thisClass = null; if (this.Owner != null) { if (this.Owner is FunctionDefinition) { thisClass = this.Owner.Owner as ClassDefinition; } else { thisClass = this.Owner as ClassDefinition; } } if (thisClass == null) { throw new ParserException(root.FirstToken, "'base' keyword can only be used inside classes."); } ClassDefinition cd = thisClass.BaseClass; if (cd == null) { throw new ParserException(root.FirstToken, "'base' keyword can only be used inside classes that extend from another class."); } FunctionDefinition fd = cd.GetMethod(field, true); if (fd == null) { throw new ParserException(this.DotToken, "Cannot find a method by that name in the base class chain."); } if (fd.IsStaticMethod) { throw new ParserException(this.DotToken, "Cannot reference static methods using 'base' keyword."); } return(new BaseMethodReference(this.FirstToken, this.DotToken, this.StepToken, this.Owner)); } if (root is ThisKeyword) { ClassDefinition cd = null; if (this.Owner != null) { if (this.Owner is FunctionDefinition) { funcDef = this.Owner as FunctionDefinition; if (funcDef.IsStaticMethod) { throw new ParserException(this.Root.FirstToken, "'this' keyword cannot be used in static methods."); } cd = funcDef.Owner as ClassDefinition; if (cd == null) { throw new ParserException(this.Root.FirstToken, "'this' keyword must be used inside a class."); } } else if (this.Owner is ClassDefinition) { cd = (ClassDefinition)this.Owner; } } if (cd == null) { throw new ParserException(this.Root.FirstToken, "'this' keyword is not allowed here."); } funcDef = cd.GetMethod(field, true); if (funcDef != null) { if (funcDef.IsStaticMethod) { throw new ParserException(this.DotToken, "This method is static and must be referenced by the class name, not 'this'."); } return(new FunctionReference(this.FirstToken, funcDef, this.Owner)); } FieldDeclaration fieldDef = cd.GetField(field, true); if (fieldDef != null) { if (fieldDef.IsStaticField) { throw new ParserException(this.DotToken, "This field is static and must be referenced by the class name, not 'this'."); } return(new FieldReference(this.FirstToken, fieldDef, this.Owner)); } // TODO: show suggestions in the error message for anything close to what was typed. throw new ParserException(this.StepToken, "The class '" + cd.NameToken.Value + "' does not have a field named '" + field + "'."); } // This is done here in the resolver instead of the parser because some unallowed // field names (such as .class) are valid. if (this.StepToken.Value == parser.Keywords.CLASS) { if (this.Root is Variable) { throw new ParserException(this.Root.FirstToken, "'" + ((Variable)this.Root).Name + "' is not a class."); } throw new ParserException(this.DotToken, ".class can only be applied to class names."); } parser.VerifyIdentifier(this.StepToken); return(this); }
public FunctionReference(Token token, FunctionDefinition funcDef, TopLevelConstruct owner) : base(token, owner) { this.FunctionDefinition = funcDef; }
private void EnsureModifiersAndTypeSignatureConsistencyForClassMethods() { ClassDefinition classDef = (ClassDefinition)this.Owner; ClassDefinition baseClass = classDef.BaseClass; bool hasBaseClass = baseClass != null; if (hasBaseClass) { FieldDefinition hiddenField = baseClass.GetField(this.NameToken.Value, true); if (hiddenField != null) { throw new ParserException(this, "This function definition hides a field from a parent class."); } } if (!hasBaseClass) { if (this.Modifiers.HasOverride) { throw new ParserException(this, "Cannot mark a method as 'override' if it has no base class."); } } else { FunctionDefinition overriddenFunction = baseClass.GetMethod(this.NameToken.Value, true); if (overriddenFunction != null) { if (!this.Modifiers.HasOverride) { if (this.CompilationScope.IsCrayon) { // TODO: just warn if not present } else { throw new ParserException(this, "This function hides another function from a parent class. If overriding is intentional, use the 'override' keyword."); } } if (overriddenFunction.Modifiers.HasStatic) { throw new ParserException(this, "Cannot override a static method."); } if (overriddenFunction.Modifiers.AccessModifierType != this.Modifiers.AccessModifierType) { throw new ParserException(this, "This method defines a different access modifier than its overridden parent."); } if (overriddenFunction.Modifiers.AccessModifierType == AccessModifierType.PRIVATE) { throw new ParserException(this, "Cannot override a private method."); } if (overriddenFunction.Modifiers.HasInternal && overriddenFunction.CompilationScope != this.CompilationScope) { throw new ParserException(this, "Cannot override this method. It is marked as internal and is located in a different assembly."); } if (overriddenFunction.ResolvedReturnType != this.ResolvedReturnType) { if (overriddenFunction.ResolvedReturnType == ResolvedType.ANY && this.ResolvedReturnType == ResolvedType.OBJECT) { // This is fine. } else { throw new ParserException(this, "This function returns a different type than its overridden parent."); } } if (overriddenFunction.ArgTypes.Length != this.ArgTypes.Length) { throw new ParserException(this, "This function has a different number of arguments than its overridden parent."); } if (overriddenFunction.MinArgCount != this.MinArgCount) { throw new ParserException(this, "This function has a different number of optional arguments than its overridden parent."); } // TODO: if you are overridding a function between a statically and dynamically typed languagte, // the dynamic language should pick up the types from the statically typed language or the // statically typed language should require "object" types in its signature for the other direction. for (int i = 0; i < this.ArgTypes.Length; ++i) { ResolvedType expected = overriddenFunction.ResolvedArgTypes[i]; ResolvedType actual = this.ResolvedArgTypes[i]; if (actual != expected && !(actual == ResolvedType.OBJECT && expected == ResolvedType.ANY)) { throw new ParserException(this, "This function has arguments that are different types than its overridden parent."); } } } else { if (this.Modifiers.HasOverride) { throw new ParserException(this, "This function is marked as 'override', but there is no method in a parent class with the same name."); } } } }
private IConstantValue SimplifyFunctionCall(FunctionDefinition func, Expression[] args) { if (args.Length == 0) { return(null); } for (int i = 0; i < args.Length; ++i) { if (!(args[i] is IConstantValue)) { return(null); } } int iValue = 0; double fValue = 0.0; bool bValue = false; double output = 0.0; string lib = func.Assembly.CanonicalKey; string name = func.NameToken.Value; Expression winner; switch (lib) { case "en:Core": switch (this.Args.Length + ":" + name) { // TODO: implement these... case "1:chr": case "1:ord": case "1:parseFloat": case "1:parseInt": case "1:typeof": return(null); case "1:isString": case "1:isNumber": if (this.Args[0] is StringConstant && name == "isString") { bValue = true; } else if (this.Args[0] is IntegerConstant || this.Args[0] is FloatConstant && name == "isNumber") { bValue = true; } return(new BooleanConstant(this.FirstToken, bValue, this.Owner)); } break; case "en:Math": double[] nums = new double[this.Args.Length]; // Make sure we're only dealing with numbers. for (int i = 0; i < this.Args.Length; ++i) { if (this.Args[i] is IntegerConstant) { nums[i] = ((IntegerConstant)this.Args[i]).Value; } else if (this.Args[i] is FloatConstant) { nums[i] = ((FloatConstant)this.Args[i]).Value; } else { throw new ParserException(this.Args[i], "This is not a valid input for " + name + "."); } } switch (this.Args.Length + ":" + name) { case "3:ensureRange": double value = nums[0]; double r1 = nums[1]; double r2 = nums[2]; if (r1 > r2) // swap the values if given in wrong order { winner = value <r2 ? args[2] : value> r1 ? args[1] : args[0]; } else { winner = value <r1 ? args[1] : value> r2 ? args[2] : args[0]; } if (winner is IntegerConstant) { return(new IntegerConstant(this.FirstToken, ((IntegerConstant)winner).Value, this.Owner)); } return(new FloatConstant(this.FirstToken, ((FloatConstant)winner).Value, this.Owner)); case "1:abs": if (args[0] is IntegerConstant) { iValue = ((IntegerConstant)args[0]).Value; return(new IntegerConstant(this.FirstToken, iValue < 0 ? -iValue : iValue, this.Owner)); } fValue = nums[0]; return(new FloatConstant(this.FirstToken, fValue < 0 ? -fValue : fValue, this.Owner)); case "2:arctan": case "2:min": case "2:max": double arg1 = nums[0]; double arg2 = nums[1]; if (name == "arctan") { output = System.Math.Atan2(arg1, arg2); if (output < 0) { output += TWO_PI; } return(new FloatConstant(this.FirstToken, output, this.Owner)); } winner = (arg1 == arg2) ? args[0] : name == "min" ? (arg1 < arg2 ? args[0] : args[1]) : (arg1 < arg2 ? args[1] : args[0]); if (winner is IntegerConstant) { return(new IntegerConstant(this.FirstToken, ((IntegerConstant)winner).Value, this.Owner)); } return(new FloatConstant(this.FirstToken, ((FloatConstant)winner).Value, this.Owner)); case "1:arctan": case "1:arcsin": case "1:arccos": case "1:floor": case "1:cos": case "1:log2": case "1:log10": case "1:sign": case "1:sin": case "1:tan": case "1:ln": if (args.Length != 1) { return(null); } double input = nums[0]; switch (name) { case "floor": return(new IntegerConstant(this.FirstToken, (int)System.Math.Floor(input), this.Owner)); case "arctan": output = System.Math.Atan(input); if (output < 0) { output += TWO_PI; } break; case "cos": output = System.Math.Cos(input); break; case "sin": output = System.Math.Sin(input); break; case "arccos": case "arcsin": if (input < -1 || input > 1) { throw new ParserException( this, "Cannot calculate " + name + " of a value outside of the range of -1 and 1"); } output = name == "arccos" ? System.Math.Acos(input) : System.Math.Asin(input); if (output < 0) { output += TWO_PI; } break; case "sign": return(new IntegerConstant( this.FirstToken, input < 0 ? -1 : input > 0 ? 1 : 0, this.Owner)); case "tan": try { output = System.Math.Tan(input); } catch (System.Exception) { throw new ParserException(this, "Cannot calculate tan of this value. tan produces undefined results."); } break; case "log10": case "log2": case "ln": if (input < 0) { throw new ParserException(this, "Cannot calculate the log of a negative number."); } output = name == "log10" ? CalculateLogWithIntegerBase(input, 10) : name == "log2" ? CalculateLogWithIntegerBase(input, 2) : System.Math.Log(input); break; } return(new FloatConstant(this.FirstToken, output, this.Owner)); } break; } return(null); }
internal override Expression ResolveTypes(ParserContext parser, TypeResolver typeResolver) { this.Root = this.Root.ResolveTypes(parser, typeResolver); for (int i = 0; i < this.Args.Length; ++i) { this.Args[i] = this.Args[i].ResolveTypes(parser, typeResolver); } ResolvedType rootType = this.Root.ResolvedType; if (rootType == ResolvedType.ANY) { this.ResolvedType = ResolvedType.ANY; return(this); } if (rootType.Category == ResolvedTypeCategory.FUNCTION_POINTER) { int maxArgCount = rootType.FunctionArgs.Length; int minArgCount = maxArgCount - rootType.FunctionOptionalArgCount; if (this.Args.Length < minArgCount || this.Args.Length > maxArgCount) { throw new ParserException(this.ParenToken, "This function has the incorrect number of arguments."); } for (int i = 0; i < this.Args.Length; ++i) { if (!this.Args[i].ResolvedType.CanAssignToA(rootType.FunctionArgs[i])) { throw new ParserException(this.Args[i], "Incorrect argument type."); } } this.ResolvedType = rootType.FunctionReturnType; if (!this.CompilationScope.IsStaticallyTyped && this.Root is FunctionReference) { FunctionDefinition fn = ((FunctionReference)this.Root).FunctionDefinition; if (fn.CompilationScope.IsStaticallyTyped) { for (int i = 0; i < this.Args.Length; ++i) { ResolvedType actualType = this.Args[i].ResolvedType; ResolvedType expectedType = fn.ResolvedArgTypes[i]; if (expectedType != ResolvedType.OBJECT && actualType == ResolvedType.ANY) { this.Args[i] = new Cast(this.Args[i].FirstToken, expectedType, this.Args[i], this.Owner, false); } } } } // TODO: this is temporary until the Math library is converted to Acrylic if (this.ResolvedType == ResolvedType.ANY && this.Root is FunctionReference && ((FunctionReference)this.Root).FunctionDefinition.CompilationScope.Metadata.ID == "Math") { FunctionReference func = (FunctionReference)this.Root; switch (func.FunctionDefinition.NameToken.Value) { case "abs": case "min": case "max": case "ensureRange": if (this.Args.Count(ex => ex.ResolvedType == ResolvedType.FLOAT) > 0) { this.ResolvedType = ResolvedType.FLOAT; } else { this.ResolvedType = ResolvedType.INTEGER; } break; case "floor": case "sign": this.ResolvedType = ResolvedType.INTEGER; break; default: this.ResolvedType = ResolvedType.FLOAT; break; } } if (this.ResolvedType == ResolvedType.ANY && this.CompilationScope.IsStaticallyTyped) { // ANY types are not allowed in statically typed compilation scopes. // Convert this into an object and require the user to perform any specific casts. this.ResolvedType = ResolvedType.OBJECT; } return(this); } throw new System.NotImplementedException(); }
internal override Expression Resolve(ParserContext parser) { for (int i = 0; i < this.Args.Length; ++i) { this.Args[i] = this.Args[i].Resolve(parser); } if (this.Root is Variable) { string varName = ((Variable)this.Root).Name; if (parser.GetClass(varName) != null) { throw new ParserException(this.ParenToken, "Cannot invoke a class like a function. To construct a new class, the \"new\" keyword must be used."); } } this.Root = this.Root.Resolve(parser); // TODO: this is hardcoded just for Math.floor(numeric constant). Eventually, it'd be nice // for a few common functions to have a compile-time codepath here. // e.g. Core.parseInt, Math.sin, etc. if (this.Root is FunctionReference && this.Args.Length == 1) { FunctionDefinition funcDef = ((FunctionReference)this.Root).FunctionDefinition; if (funcDef.Library != null && funcDef.Library.CanonicalKey == "en:Math" && funcDef.NameToken.Value == "floor") { Expression arg0 = this.Args[0]; if (arg0 is IntegerConstant) { int integerValue = ((IntegerConstant)arg0).Value; return(new IntegerConstant(this.FirstToken, integerValue, this.Owner)); } if (arg0 is FloatConstant) { double floatValue = ((FloatConstant)arg0).Value; int integerValue = (int)floatValue; return(new IntegerConstant(this.FirstToken, integerValue, this.Owner)); } } } if (this.Root is SpecialEntity) { if (this.Root is SpecialEntity.EnumMaxFunction) { int max = ((SpecialEntity.EnumMaxFunction) this.Root).GetMax(); return(new IntegerConstant(this.Root.FirstToken, max, this.Owner)); } if (this.Root is SpecialEntity.EnumValuesFunction) { int[] rawValues = ((SpecialEntity.EnumValuesFunction) this.Root).GetValues(); List <Expression> values = new List <Expression>(); foreach (int rawValue in rawValues) { values.Add(new IntegerConstant(this.Root.FirstToken, rawValue, this.Owner)); } return(new ListDefinition(this.FirstToken, values, this.Owner)); } } return(this); }
internal override void Resolve(ParserContext parser) { for (int i = 0; i < this.Fields.Length; ++i) { FieldDefinition field = this.Fields[i]; field.Resolve(parser); this.Fields[i] = field; } for (int i = 0; i < this.Methods.Length; ++i) { FunctionDefinition funcDef = this.Methods[i]; funcDef.Resolve(parser); this.Methods[i] = funcDef; } this.Constructor.Resolve(parser); if (this.StaticConstructor != null) { this.StaticConstructor.Resolve(parser); } bool hasABaseClass = this.BaseClass != null; bool callsBaseConstructor = this.Constructor.BaseToken != null; if (hasABaseClass && callsBaseConstructor) { Expression[] defaultValues = this.BaseClass.Constructor.DefaultValues; int maxValues = defaultValues.Length; int minValues = 0; for (int i = 0; i < maxValues; ++i) { if (defaultValues[i] == null) { minValues++; } else { break; } } int baseArgs = this.Constructor.BaseArgs.Length; if (baseArgs < minValues || baseArgs > maxValues) { throw new ParserException(this.Constructor.BaseToken, "Invalid number of arguments passed to base constructor."); } } else if (hasABaseClass && !callsBaseConstructor) { if (this.BaseClass.Constructor != null) { if (this.BaseClass.Constructor.MinArgCount > 0) { throw new ParserException(this, "The base class of this class has a constructor which must be called."); } } } else if (!hasABaseClass && callsBaseConstructor) { throw new ParserException(this.Constructor.BaseToken, "Cannot call base constructor without a base class."); } else // (!hasABaseClass && !callsBaseConstructor) { // yeah, that's fine. } }