internal override void EnsureModifierAndTypeSignatureConsistency() { bool isStatic = this.Modifiers.HasStatic; ClassDefinition classDef = (ClassDefinition)this.Owner; ClassDefinition baseClass = classDef.BaseClass; bool hasBaseClass = baseClass != null; if (!isStatic && classDef.Modifiers.HasStatic && !this.IsDefault) { throw new ParserException(this, "Cannot have a non-static constructor in a static class."); } if (this.BaseToken != null) { if (isStatic) { throw new ParserException(this.BaseToken, "Cannot invoke the base constructor from a static constructor."); } if (!hasBaseClass) { throw new ParserException(this.BaseToken, "There is no base class for this constructor to invoke."); } } if (hasBaseClass && !Node.IsAccessAllowed(this, baseClass.Constructor)) { throw new ParserException(this, "Cannot invoke the base constructor due to its access modifier."); } }
internal override Expression ResolveEntityNames(ParserContext parser) { this.BatchExpressionEntityNameResolver(parser, this.Args); // TODO: localize or create a dummy stub in Core if (this.Name == "List") { return(new ListDefinition(this.FirstToken, new List <Expression>(), this.Generics[0], this.Owner, false, null)); } else if (this.Name == "Dictionary") { return(new DictionaryDefinition( this.FirstToken, this.Generics[0], this.Generics[1], new List <Expression>(), new List <Expression>(), this.Owner)); } this.Class = this.FileScope.DoClassLookup(this.Owner, this.NameToken, this.Name); if (this.Class == null) { throw new ParserException(this, "No class named '" + this.Name + "'"); } if (this.Class.Modifiers.HasStatic) { throw new ParserException(this, "Cannot instantiate a static class."); } Node.EnsureAccessIsAllowed(this.FirstToken, this.Owner, this.Class); if (!Node.IsAccessAllowed(this.Owner, this.Class.Constructor)) { // TODO: word this better. Maybe just say "this class' constructor is marked as private/protected/etc" throw new ParserException(this.FirstToken, "This class' constructor's access modifier does not allow it to be constructed here."); } this.ConstructorReference = this.Class.Constructor; int minArgCount = 0; int maxArgCount = 0; bool isPrivate = false; if (this.ConstructorReference != null) { minArgCount = this.ConstructorReference.MinArgCount; maxArgCount = this.ConstructorReference.MaxArgCount; isPrivate = this.ConstructorReference.Annotations.IsPrivate(); } if (isPrivate) { bool isValidUsage = this.Class == this.Owner || // used in a field where the owner is the class directly this.Class == this.Owner.Owner; // used in a function where the owner is the method whose owner is the class. if (!isValidUsage) { throw new ParserException(this, "The constructor for " + this.Class.NameToken.Value + " is private and cannot be invoked from outside the class."); } } if (this.Args.Length < minArgCount || this.Args.Length > maxArgCount) { string message = "This constructor has the wrong number of arguments. "; if (minArgCount == maxArgCount) { message += "Expected " + minArgCount + " but found " + this.Args.Length; } else if (this.Args.Length < minArgCount) { message += " At least " + minArgCount + " are required but found only " + this.Args.Length + "."; } else { message += " At most " + maxArgCount + " are allowed but found " + this.Args.Length + "."; } throw new ParserException(this, message); } 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."); } }