Exemplo n.º 1
0
        /// <summary>
        /// Pick a random sample from the set, optionally with the specified gender.
        /// If a sample is rare it will be picked less often (using the rarity value).
        /// </summary>
        public Sample Pick(Sample.Genders gender = Sample.Genders.NEUTRAL)
        {
            if (!HasGender && gender != Sample.Genders.NEUTRAL)
            {
                throw new Exception("Cannot pick a gendered sample from a set of un-gendered samples");
            }

            // If gender is not specified choose male or female at random
            if (HasGender && gender == Sample.Genders.NEUTRAL)
            {
                if (random.Next(2) == 0)
                {
                    gender = Sample.Genders.MALE;
                }
                else
                {
                    gender = Sample.Genders.FEMALE;
                }
            }

            // Copy the sample list so we can pick at random and
            // remove so we don't pick the same sample again.
            List <Sample> samples;

            if (!HasGender || gender == Sample.Genders.MALE)
            {
                samples = new List <Sample>(SampleList);
            }
            else
            {
                samples = new List <Sample>(FemaleSampleList);
            }

            // Pick a sample and, if it's rare, apply rarity percent and
            // if it isn't chosen, remove it from the list and pick again.
            // There is always at least one non-rare sample so the list
            // will never be exhausted.
            while (true)
            {
                int    pickPos = random.Next(samples.Count);
                Sample sample  = samples[pickPos];

                if (sample.Rarity > 0)
                {
                    // Roll the dice to see if we pick this rare sample
                    if (random.Next(100) >= sample.Rarity)
                    {
                        // Remove from list and pick again
                        samples.RemoveAt(pickPos);
                        continue;
                    }
                }

                // Got our sample
                return(sample);
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Ref function
        /// </summary>
        public string FuncRef(string called, string[] args, Cache cache, string parent, IResolver resolver,
                              out Sample.Genders gender)
        {
            string help = "Use func.ref(--help) for help.";

            if (args.Length == 1 && args[0].Equals("--help", StringComparison.CurrentCultureIgnoreCase))
            {
                // Show usage
                string usage = "Usage: func.ref(arg) where arg is another attribute to take the value from.";
                throw new Exception(usage);
            }

            if (args.Length != 1)
            {
                throw new Exception($"{called} has bad number of arguments. {help}");
            }

            if (args[0].Length == 0)
            {
                throw new Exception($"{called} has empty argument. {help}");
            }

            ResolvedValue refValue;

            try
            {
                refValue = GetRefValue(args[0], cache, parent, resolver);
            }
            catch (Exception e)
            {
                throw new Exception($"{called} {e.Message}. {help}");
            }

            gender = refValue.Gender;
            return(refValue.Value);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Evaluates a single function. Function name must be a known
        /// function or an exception will be thrown.
        ///
        /// The arg list is resolved (as a whole) before being split into
        /// separate arguments so it may contain embedded func.xxx() calls.
        /// </summary>
        private string eval(string funcName, string argList, Cache cache, string parentName, out Sample.Genders gender)
        {
            string called = $"func.{funcName}({argList})";

            // Validate function name
            Funcs.FuncNames func;
            try
            {
                func = (Funcs.FuncNames)Enum.Parse(typeof(Funcs.FuncNames), funcName.Trim(), true);
            }
            catch (Exception)
            {
                throw new Exception($"Unknown function 'func.{funcName}(...)'. Must be one of: {String.Join(", ", Enum.GetNames(typeof(Funcs.FuncNames)))}");
            }

            // Resolve arg list (in case funcs are being passed as args to other funcs) but don't add
            // to cache as we want them to evaluate differently each time (if they're random).
            string nocacheParent;
            int    sep = parentName.IndexOf('.');

            if (sep == -1)
            {
                nocacheParent = parentName + ".NOCACHE";
            }
            else
            {
                nocacheParent = parentName.Substring(0, sep) + ".NOCACHE";
            }
            var resolvingArgs = new ResolvedValue(nocacheParent, Entity.Types.STR, argList);

            ResolveValue(resolvingArgs, cache);
            gender = resolvingArgs.Gender;

            // Replace escaped brackets and split up arguments
            var args = SplitArgs(resolvingArgs.Value.Replace("\\(", "(").Replace("\\)", ")").Trim());

            // Call the relevant function
            string result;

            Sample.Genders newGender;
            switch (func)
            {
            case Funcs.FuncNames.DATE:
                return(this.funcs.FuncDate(called, args));

            case Funcs.FuncNames.GEN:
                return(this.funcs.FuncGen(called, args));

            case Funcs.FuncNames.IF:
                return(this.funcs.FuncIf(called, args, cache, parentName, this, this.formatter));

            case Funcs.FuncNames.MATH:
                return(this.funcs.FuncMath(called, args, cache, parentName, this));

            case Funcs.FuncNames.NUM:
                return(this.funcs.FuncNum(called, args, cache, parentName, this));

            case Funcs.FuncNames.PICK:
                return(this.funcs.FuncPick(called, args));

            case Funcs.FuncNames.RAND:
                return(this.funcs.FuncRand(called, args));

            case Funcs.FuncNames.REF:
                result = this.funcs.FuncRef(called, args, cache, parentName, this, out newGender);
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }
                return(result);

            case Funcs.FuncNames.SAMPLE:
                result = this.funcs.FuncSample(called, args, cache, parentName, this, out newGender);
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }
                return(result);

            case Funcs.FuncNames.SPLIT:
                return(this.funcs.FuncSplit(called, args, cache, parentName, this));

            case Funcs.FuncNames.STR:
                return(this.funcs.FuncStr(called, args));

            case Funcs.FuncNames.TIME:
                return(this.funcs.FuncTime(called, args));

            default:
                throw new Exception($"Missing function name: {func}");
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Takes a literal string and looks for any embedded
        /// func.xxx(...) calls. Each embedded function is resolved
        /// and the result is included in the returned string.
        ///
        /// Throws an exception if the value cannot be resolved
        /// </summary>
        private void ResolveValue(ResolvedValue resolving, Cache cache)
        {
            // See if it's already been resolved
            var resolved = cache.GetResolved(resolving.Name);

            if (resolved != null)
            {
                resolving.SetValue(resolved.Value, resolved.Gender);
                return;
            }

            if (resolving.UnresolvedValue == null)
            {
                throw new Exception("Unresolved value cannot be null");
            }

            // If value is already concrete it's resolved
            string value = resolving.UnresolvedValue;

            if (!value.Contains("func."))
            {
                resolving.SetValue(value);
                cache.SetResolved(resolving);
                return;
            }

            StringBuilder resolvedStr = new StringBuilder();

            Sample.Genders gender = Sample.Genders.NEUTRAL;

            int idx = 0;

            while (idx < value.Length)
            {
                int funcPos = value.IndexOf("func.", idx, StringComparison.CurrentCultureIgnoreCase);
                if (funcPos == -1)
                {
                    resolvedStr.Append(value.Substring(idx));
                    break;
                }

                resolvedStr.Append(value.Substring(idx, funcPos - idx));

                // Find function name
                idx = funcPos + 5;
                int argStart = value.IndexOf('(', idx);
                if (argStart == -1)
                {
                    throw new Exception($"Function 'func.{value.Substring(idx)}' has missing opening bracket");
                }
                string funcName = value.Substring(idx, argStart - idx).Trim();

                // Find matching close bracket
                argStart++;
                int argEnd       = argStart;
                int openBrackets = 0;
                while (true)
                {
                    if (argEnd == value.Length)
                    {
                        throw new Exception($"Function 'func.{value.Substring(idx)}' has missing closing bracket");
                    }

                    // Ignore escaped brackets
                    if (value[argEnd] == '\\' && argEnd < value.Length - 1 && (value[argEnd + 1] == '(' || value[argEnd + 1] == ')'))
                    {
                        argEnd += 2;
                        continue;
                    }

                    if (value[argEnd] == '(')
                    {
                        openBrackets++;
                    }
                    else if (value[argEnd] == ')')
                    {
                        if (openBrackets == 0)
                        {
                            break;
                        }
                        else
                        {
                            openBrackets--;
                        }
                    }

                    argEnd++;
                }

                string argList = value.Substring(argStart, argEnd - argStart);
                string parentName;
                int    pos = resolving.Name == null? -1 : resolving.Name.LastIndexOf('.');
                if (pos == -1)
                {
                    parentName = resolving.Name;
                }
                else
                {
                    parentName = resolving.Name.Substring(0, pos);
                }

                resolvedStr.Append(eval(funcName, argList, cache, parentName, out var newGender));
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }

                idx = argEnd + 1;
            }

            resolving.SetValue(resolvedStr.ToString(), gender);
            cache.SetResolved(resolving);
        }
Exemplo n.º 5
0
        public Samples LoadSamples(string name, string[] lines)
        {
            int  numCols          = 1;
            int  genderCol        = 0;
            int  rarityCol        = 0;
            bool hasMaleNonRare   = false;
            bool hasFemaleNonRare = false;
            int  i = 0;

            // Skip blank lines and comments that don't contain a comma
            while (i < lines.Length && (lines[i].Trim().Length == 0 || (lines[i][0] == '#' && !lines[i].Contains(','))))
            {
                i++;
            }

            // Is there a header?
            if (i < lines.Length && lines[i][0] == '#' && lines[i].Contains(','))
            {
                var colNames = lines[i].Split(',');
                numCols = colNames.Length;

                if (numCols > 3)
                {
                    throw new Exception($"Multi-column samples '{name}' header must have 2 or 3 columns");
                }

                // Don't validate name of first column (can be anything)
                for (int col = 1; col < numCols; col++)
                {
                    string  colName = colNames[col].Trim();
                    Columns colType;
                    try
                    {
                        colType = (Columns)Enum.Parse(typeof(Columns), colName, true);
                    }
                    catch (ArgumentException)
                    {
                        throw new Exception($"Multi-column samples '{name}' header has invalid column name '{colName}' - " +
                                            $"must be one of: {String.Join(", ", Enum.GetNames(typeof(Columns)))}");
                    }

                    switch (colType)
                    {
                    case Columns.GENDER:
                        genderCol = col;
                        break;

                    case Columns.RARITY:
                        rarityCol = col;
                        break;

                    default:
                        throw new Exception($"SamplesParser missing code for column type: {colType}");
                    }
                }

                i++;
            }

            var samples = new Samples(name, genderCol != 0);

            while (i < lines.Length)
            {
                string value = lines[i].Trim();

                // Skip empty lines and comments
                if (value.Length == 0 || value[0] == '#')
                {
                    i++;
                    continue;
                }

                Sample.Genders gender = Sample.Genders.NEUTRAL;
                int            rarity = 0;

                if (numCols > 1)
                {
                    var columns = value.Split(',');

                    if (columns.Length > numCols)
                    {
                        throw new Exception($"Multi-column samples file '{name}' line {i + 1} " +
                                            $"has too many columns (or embedded comma) - must match number of columns in header.");
                    }

                    string[] colData = new string[numCols];

                    for (int col = 0; col < numCols; col++)
                    {
                        if (col < columns.Length)
                        {
                            colData[col] = columns[col].Trim();
                        }
                        else
                        {
                            colData[col] = "";
                        }
                    }

                    value = colData[0];
                    if (value.Length == 0)
                    {
                        throw new Exception($"Multi-column samples file '{name}' line {i + 1} " +
                                            $"value cannot be blank. Use <null> if you want a null value.");
                    }

                    if (genderCol != 0)
                    {
                        string genderStr = colData[genderCol].ToUpper();

                        if (genderStr.Length > 0)
                        {
                            if (genderStr.Equals("M"))
                            {
                                gender = Sample.Genders.MALE;
                            }
                            else if (genderStr.Equals("F"))
                            {
                                gender = Sample.Genders.FEMALE;
                            }
                            else
                            {
                                throw new Exception($"Multi-column samples file '{name}' line {i + 1} " +
                                                    $"has gender '{genderStr}' but expected M or F");
                            }
                        }
                    }

                    if (rarityCol != 0)
                    {
                        string rarityStr = colData[rarityCol];

                        if (rarityStr.Length > 0)
                        {
                            try
                            {
                                rarity = int.Parse(rarityStr);
                            }
                            catch (Exception)
                            {
                                throw new Exception($"Multi-column samples file '{name}' line {i + 1} " +
                                                    $"has non-integer rarity '{rarityStr}'");
                            }

                            if (rarity < 1 || rarity > 99)
                            {
                                throw new Exception($"Multi-column samples file '{name}' line {i + 1} " +
                                                    $"has rarity {rarity} but expected either blank or value between 1 and 99");
                            }
                        }
                    }
                }

                if ((gender == Sample.Genders.MALE || gender == Sample.Genders.NEUTRAL) && rarity == 0)
                {
                    hasMaleNonRare = true;
                }

                if ((gender == Sample.Genders.FEMALE || gender == Sample.Genders.NEUTRAL) && rarity == 0)
                {
                    hasFemaleNonRare = true;
                }

                samples.AddSample(new Sample(value, gender, rarity));
                i++;
            }

            // Samples must contain at least one sample. If gendered, must be
            // true for both male and female. If rarity, there must be at least
            // one non-rare sample.
            if (!hasMaleNonRare)
            {
                StringBuilder modifier = new StringBuilder();
                if (genderCol != 0)
                {
                    modifier.Append(" Male");
                }
                if (rarityCol != 0)
                {
                    modifier.Append(modifier.Length == 0? " " : ", ");
                    modifier.Append("Non-rare (rarity left blank)");
                }

                throw new Exception($"Samples file '{name}' " +
                                    $"must have at least one{modifier} sample");
            }

            if (genderCol != 0 && !hasFemaleNonRare)
            {
                StringBuilder modifier = new StringBuilder();
                if (genderCol != 0)
                {
                    modifier.Append(" Female");
                }
                if (rarityCol != 0)
                {
                    modifier.Append(modifier.Length == 0 ? " " : ", ");
                    modifier.Append("Non-rare (rarity left blank)");
                }

                throw new Exception($"Multi-column samples file '{name}' " +
                                    $"must have at least one{modifier} sample");
            }

            return(samples);
        }
Exemplo n.º 6
0
 public void SetValue(string value, Sample.Genders gender = Sample.Genders.NEUTRAL)
 {
     Value  = value;
     Gender = gender;
 }
Exemplo n.º 7
0
        /// <summary>
        /// Sample function
        ///
        /// The sample function has a circular dependency on the resolver (so we can
        /// have samples based on the gender of other fields which may not be resolved
        /// yet) so we have to pass the resolver in as a parameter.
        /// </summary>
        public string FuncSample(string called, string[] args, Cache cache, string parent, IResolver resolver,
                                 out Sample.Genders gender)
        {
            string help = "Use func.sample(--help) for help.";

            if (args.Length == 1 && args[0].Equals("--help", StringComparison.CurrentCultureIgnoreCase))
            {
                // Show usage
                string usage = "Usage: func.sample(arg1, [arg2]) where arg1 is the name of the samples file to use and arg2 is " +
                               "either another attribute to take the gender from, or M or F to use a fixed gender.";
                throw new Exception(usage);
            }

            if (args.Length < 1 || args.Length > 2)
            {
                throw new Exception($"{called} has bad number of arguments. {help}");
            }

            if (args[0].Length == 0)
            {
                throw new Exception($"{called} has empty argument. {help}");
            }

            gender = Sample.Genders.NEUTRAL;
            if (args.Length == 2)
            {
                if (args[1].Equals("M", StringComparison.CurrentCultureIgnoreCase))
                {
                    gender = Sample.Genders.MALE;
                }
                else if (args[1].Equals("F", StringComparison.CurrentCultureIgnoreCase))
                {
                    gender = Sample.Genders.FEMALE;
                }
                else
                {
                    // Inherit gender from referenced attribute.
                    string refName;
                    if (args[1].Length > 1 && args[1][0] == '~')
                    {
                        refName = parent + "." + args[1].Substring(1);
                    }
                    else
                    {
                        refName = parent + "." + args[1];
                    }

                    Entity refAttrib;
                    try
                    {
                        refAttrib = resolver.FindEntity(refName);
                    }
                    catch (Exception)
                    {
                        throw new Exception($"{called} references unknown attribute '{refName}'");
                    }

                    var refValue = new ResolvedValue(refName, refAttrib.Type, refAttrib.Value);
                    try
                    {
                        resolver.Resolve(refValue, cache);
                    }
                    catch (Exception e)
                    {
                        throw new Exception($"{called} references unresolvable attribute '{refName}': {e.Message}");
                    }

                    gender = refValue.Gender;
                }
            }

            var samples = resolver.GetSamples(args[0]);
            var sample  = samples.Pick(gender);

            gender = sample.Gender;
            return(sample.Value);
        }