示例#1
0
		/// <summary>
		/// Determines if the specified dictionary entry passes the filter.
		/// </summary>
		/// <param name="entry">The entry to test.</param>
		/// <param name="exclusive">Specifies whether the search is exclusive.</param>
		/// <returns></returns>
		public bool Test(RantDictionaryEntry entry, bool exclusive = false)
		{
			return exclusive
				? _items.Any() == entry.GetClasses().Any() 
					&& entry.GetClasses().All(c => _items.Any(item => item.Any(rule => rule.ShouldMatch && rule.Class == c)))
				: !_items.Any() || _items.All(set => set.Any(rule => entry.ContainsClass(rule.Class) == rule.ShouldMatch));
		}
示例#2
0
 /// <summary>
 /// Determines if the specified dictionary entry passes the filter.
 /// </summary>
 /// <param name="entry">The entry to test.</param>
 /// <param name="exclusive">Specifies whether the search is exclusive.</param>
 /// <returns></returns>
 public bool Test(RantDictionaryEntry entry, bool exclusive = false)
 {
     return(exclusive
                         ? _items.Any() == entry.GetClasses().Any() &&
            entry.GetClasses().All(c => _items.Any(item => item.Any(rule => rule.ShouldMatch && rule.Class == c)))
                         : !_items.Any() || _items.All(set => set.Any(rule => entry.ContainsClass(rule.Class) == rule.ShouldMatch)));
 }
示例#3
0
 public bool Test(RantDictionaryEntry entry)
 {
     if (!EntryTypeDefFilter.Test(Filter, entry))
     {
         return(true);
     }
     return(entry.GetClasses().Where(IsValidValue).Count() == 1);
 }
示例#4
0
 /// <summary>
 /// Removes the specified entry from the table.
 /// </summary>
 /// <param name="entry">The entry to remove from the table.</param>
 /// <returns>True if successfully removed; otherwise, False.</returns>
 public bool RemoveEntry(RantDictionaryEntry entry)
 {
     if (entry == null)
     {
         throw new ArgumentNullException(nameof(entry));
     }
     if (!_entriesHash.Remove(entry))
     {
         return(false);
     }
     _entriesList.Remove(entry);
     _dirty = true;
     return(true);
 }
示例#5
0
 /// <summary>
 /// Adds the specified entry to the table.
 /// </summary>
 /// <param name="entry">The entry to add to the table.</param>
 /// <returns>True if successfully added; otherwise, False.</returns>
 public bool AddEntry(RantDictionaryEntry entry)
 {
     if (entry == null)
     {
         throw new ArgumentNullException(nameof(entry));
     }
     if (entry.TermCount != TermsPerEntry)
     {
         return(false);
     }
     if (!_entriesHash.Add(entry))
     {
         return(false);
     }
     _entriesList.Add(entry);
     _dirty = true;
     return(true);
 }
示例#6
0
        public static bool AssociatesWith(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null) return false;

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired) return true; // If both have no required classes, pass.

            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should be either empty, or have exactly the same classes.
            return !aRequired.Except(bRequired).Any() && aRequired.Any() == bRequired.Any();
        }
示例#7
0
        public static bool RelatesWith(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null) return false;

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired) return true; // If both have no required classes, pass.

            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should share at least one class.
            return aRequired.Intersect(bRequired).Any();
        }
示例#8
0
        public static bool AssociatesWith(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null)
            {
                return(false);
            }

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired)
            {
                return(true);                                // If both have no required classes, pass.
            }
            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should be either empty, or have exactly the same classes.
            return(!aRequired.Except(bRequired).Any() && aRequired.Any() == bRequired.Any());
        }
示例#9
0
        public static bool DivergesFrom(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null)
            {
                return(false);
            }

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired)
            {
                return(true);                                // If both have no required classes, pass.
            }
            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should be either empty, or differ by at least one class.
            return(aRequired.Except(bRequired).Any() || bRequired.Except(aRequired).Any());
        }
示例#10
0
        public static bool RelatesWith(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null)
            {
                return(false);
            }

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired)
            {
                return(true);                                // If both have no required classes, pass.
            }
            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should share at least one class.
            return(aRequired.Intersect(bRequired).Any());
        }
示例#11
0
 /// <summary>
 /// Determines whether a type should apply to the specifed entry according to the specified filter.
 /// </summary>
 /// <param name="filter">The filter to test with.</param>
 /// <param name="entry">The entry to test.</param>
 /// <returns></returns>
 public static bool Test(EntryTypeDefFilter filter, RantDictionaryEntry entry) => filter?.DoTest(entry) ?? false;
        /// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path)
        {
            var name = "";

            string[] subtypes = { "default" };
            bool     header   = true;

            var scopedClassSet        = new HashSet <string>();
            RantDictionaryEntry entry = null;
            var entries       = new List <RantDictionaryEntry>();
            var entryStringes = new List <Stringe>();
            var types         = new Dictionary <string, EntryTypeDef>();
            var hiddenClasses = new HashSet <string> {
                "nsfw"
            };

            foreach (var token in DicLexer.Tokenize(path, File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                case DicTokenType.Directive:
                {
                    var parts = VocabUtils.GetArgs(token.Value).ToArray();
                    if (!parts.Any())
                    {
                        continue;
                    }
                    var dirName = parts.First().ToLower();
                    var args    = parts.Skip(1).ToArray();

                    switch (dirName)
                    {
                    case "name":
                        if (!header)
                        {
                            LoadError(path, token, "The #name directive may only be used in the file header.");
                        }
                        if (args.Length != 1)
                        {
                            LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                        }
                        if (!Util.ValidateName(args[0]))
                        {
                            LoadError(path, token, $"Invalid #name value: '{args[1]}'");
                        }
                        name = args[0].ToLower();
                        break;

                    case "subs":
                        if (!header)
                        {
                            LoadError(path, token, "The #subs directive may only be used in the file header.");
                        }
                        subtypes = args.Select(s => s.Trim().ToLower()).ToArray();
                        break;

                    case "version":             // Kept here for backwards-compatability
                        if (!header)
                        {
                            LoadError(path, token, "The #version directive may only be used in the file header.");
                        }
                        break;

                    case "hidden":
                        if (!header)
                        {
                            LoadError(path, token, "The #hidden directive may only be used in the file header.");
                        }
                        if (Util.ValidateName(args[0]))
                        {
                            hiddenClasses.Add(args[0]);
                        }
                        break;

                    // Deprecated, remove in Rant 3
                    case "nsfw":
                        scopedClassSet.Add("nsfw");
                        break;

                    // Deprecated, remove in Rant 3
                    case "sfw":
                        scopedClassSet.Remove("nsfw");
                        break;

                    case "class":
                    {
                        if (args.Length < 2)
                        {
                            LoadError(path, token, "The #class directive expects an operation and at least one value.");
                        }
                        switch (args[0].ToLower())
                        {
                        case "add":
                            foreach (var cl in args.Skip(1))
                            {
                                scopedClassSet.Add(cl.ToLower());
                            }
                            break;

                        case "remove":
                            foreach (var cl in args.Skip(1))
                            {
                                scopedClassSet.Remove(cl.ToLower());
                            }
                            break;
                        }
                    }
                    break;

                    case "type":
                    {
                        if (!header)
                        {
                            LoadError(path, token, "The #type directive may only be used in the file header.");
                        }
                        if (args.Length != 3)
                        {
                            LoadError(path, token, "#type directive requires 3 arguments.");
                        }
                        types.Add(args[0], new EntryTypeDef(args[0], args[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries),
                                                            Util.IsNullOrWhiteSpace(args[2]) ? null : new EntryTypeDefFilter(args[2])));
                    }
                    break;
                    }
                }
                break;

                case DicTokenType.Entry:
                {
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing table name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty entry.");
                    }
                    header = false;
                    entry  = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet);
                    entries.Add(entry);
                    entryStringes.Add(token);
                }
                break;

                case DicTokenType.DiffEntry:
                {
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing table name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty entry.");
                    }
                    header = false;
                    string first = null;
                    entry = new RantDictionaryEntry(token.Value.Split('/')
                                                    .Select((s, i) =>
                        {
                            if (i > 0)
                            {
                                return(Diff.Mark(first, s));
                            }
                            return(first = s.Trim());
                        }).ToArray(), scopedClassSet);
                    entries.Add(entry);
                    entryStringes.Add(token);
                }
                break;

                case DicTokenType.Property:
                {
                    var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        LoadError(path, token, "Empty property field.");
                    }
                    switch (parts[0].ToLower())
                    {
                    case "class":
                    {
                        if (parts.Length < 2)
                        {
                            continue;
                        }
                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            bool opt = cl.EndsWith("?");
                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                        }
                    }
                    break;

                    case "weight":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'weight' property expected a value.");
                        }
                        int weight;
                        if (!Int32.TryParse(parts[1], out weight))
                        {
                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                        }
                        entry.Weight = weight;
                    }
                    break;

                    case "pron":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                        }
                        var pron =
                            parts[1].Split('/')
                            .Select(s => s.Trim())
                            .ToArray();
                        if (subtypes.Length == pron.Length)
                        {
                            for (int i = 0; i < entry.Terms.Length; i++)
                            {
                                entry.Terms[i].Pronunciation = pron[i];
                            }
                        }
                    }
                    break;

                    default:
                    {
                        EntryTypeDef typeDef;
                        if (!types.TryGetValue(parts[0], out typeDef))
                        {
                            LoadError(path, token, $"Unknown property name '{parts[0]}'.");
                        }
                        // Okay, it's a type.
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "Missing type value.");
                        }
                        entry.AddClass(VocabUtils.GetString(parts[1]));
                        if (!typeDef.IsValidValue(parts[1]))
                        {
                            LoadError(path, token, $"'{parts[1]}' is not a valid value for type '{typeDef.Name}'.");
                        }
                        break;
                    }
                    }
                }
                break;
                }
            }

            if (types.Any())
            {
                var eEntries       = entries.GetEnumerator();
                var eEntryStringes = entryStringes.GetEnumerator();
                while (eEntries.MoveNext() && eEntryStringes.MoveNext())
                {
                    foreach (var type in types.Values)
                    {
                        if (!type.Test(eEntries.Current))
                        {
                            // TODO: Find a way to output multiple non-fatal table load errors without making a gigantic exception message.
                            LoadError(path, eEntryStringes.Current, $"Entry '{eEntries.Current}' does not satisfy type '{type.Name}'.");
                        }
                    }
                }
            }

            return(new RantDictionaryTable(name, subtypes, entries, hiddenClasses));
        }
示例#13
0
 /// <summary>
 /// Determines whether a type should apply to the specifed entry according to the specified filter.
 /// </summary>
 /// <param name="filter">The filter to test with.</param>
 /// <param name="entry">The entry to test.</param>
 /// <returns></returns>
 public static bool Test(EntryTypeDefFilter filter, RantDictionaryEntry entry) => filter?.DoTest(entry) ?? false;
示例#14
0
        /// <summary>
        /// Loads a package from the specified stream and returns it as a RantPackage object.
        /// </summary>
        /// <param name="source">The stream to load the package data from.</param>
        /// <returns></returns>
        public static RantPackage Load(Stream source)
        {
            using (var reader = new EasyReader(source))
            {
                var magic = Encoding.ASCII.GetString(reader.ReadBytes(4));
                if (magic == OLD_MAGIC)
                    return LoadOldPackage(reader);
                if (magic != MAGIC)
                    throw new InvalidDataException("File is corrupt.");
                var package = new RantPackage();
                var version = reader.ReadUInt32();
                if (version != PACKAGE_VERSION)
                    throw new InvalidDataException("Invalid package version: " + version);
                var compress = reader.ReadBoolean();
                var size = reader.ReadInt32();
                var data = reader.ReadBytes(size);
                if (compress)
                    data = EasyCompressor.Decompress(data);
                var doc = BsonDocument.Read(data);

                var info = doc["info"];
				if (info == null)
					throw new InvalidDataException("Metadata is missing from package.");

				package.Title = info["title"];
				package.ID = info["id"];
				package.Version = RantPackageVersion.Parse(info["version"]);
				package.Description = info["description"];
	            package.Authors = (string[])info["authors"];
				package.Tags = (string[])info["tags"];
				var deps = info["dependencies"];
				if (deps != null && deps.IsArray)
				{
					for(int i = 0; i < deps.Count; i++)
					{
						var dep = deps[i];
						var depId = dep["id"].Value;
						var depVersion = dep["version"].Value;
						bool depAllowNewer = (bool)dep["allow-newer"].Value;
						package.AddDependency(new RantPackageDependency(depId.ToString(), depVersion.ToString()) { AllowNewer = depAllowNewer });
					}
				}

				var patterns = doc["patterns"];
                if(patterns != null)
                {
                    var names = patterns.Keys;
                    foreach (string name in names)
                        package.AddPattern(new RantPattern(name, RantPatternOrigin.File, patterns[name]));
                }

                var tables = doc["tables"];
                if(tables != null)
                {
                    var names = tables.Keys;
                    foreach(string name in names)
                    {
                        var table = tables[name];
                        string tableName = table["name"];
                        string[] tableSubs = (string[])table["subs"];
                        string[] hiddenClasses = (string[])table["hidden"];

                        var entries = new List<RantDictionaryEntry>();
                        var entryList = table["entries"];
                        for(var i = 0; i < entryList.Count; i++)
                        {
                            var loadedEntry = entryList[i];
                            int weight = 1;
                            if (loadedEntry.HasKey("weight"))
                                weight = (int)loadedEntry["weight"].Value;
                            string[] requiredClasses = (string[])loadedEntry["classes"];
                            string[] optionalClasses = (string[])loadedEntry["optional_classes"];
                            var terms = new List<RantDictionaryTerm>();
                            var termList = loadedEntry["terms"];
                            for(var j = 0; j < termList.Count; j++)
                            {
                                var t = new RantDictionaryTerm(termList[j]["value"], termList[j]["pron"]);
                                terms.Add(t);
                            }
                            var entry = new RantDictionaryEntry(
                                terms.ToArray(),
                                requiredClasses.Concat(optionalClasses.Select(x => x + "?")),
                                weight
                            );
                            entries.Add(entry);
                        }
                        var rantTable = new RantDictionaryTable(
                            tableName,
                            tableSubs,
                            entries,
                            hiddenClasses
                        );
                        package.AddTable(rantTable);
                    }
                }

                return package;
            }
        }
示例#15
0
 private bool DoTest(RantDictionaryEntry entry)
 {
     return(_filterParts == null || _filterParts.All(f => entry.ContainsClass(f.Item1) == f.Item2));
 }
示例#16
0
 // Returns the classes of an object, but optional classes are postfixed with ?
 private static IEnumerable <string> GetClassesForExport(RantDictionaryEntry entry)
 {
     return(entry.GetRequiredClasses().Concat(entry.GetOptionalClasses().Select(x => x + "?")));
 }
示例#17
0
        internal RantDictionaryEntry GetEntry(Carrier carrier, int subtypeIndex, IEnumerable <RantDictionaryEntry> pool, RNG rng)
        {
            if (carrier == null)
            {
                return(pool.PickEntry(rng));
            }

            RantDictionaryEntry result = null;

            // Handle match carriers
            foreach (var match in carrier.GetCarriers(CarrierComponent.Match))
            {
                if (_matchTable.TryGetValue(match, out result))
                {
                    return(result);
                }
            }

            // Handle associative carriers
            foreach (var assoc in carrier.GetCarriers(CarrierComponent.Associative))
            {
                if (_assocTable.TryGetValue(assoc, out result))
                {
                    pool = pool.Where(e => e.AssociatesWith(result));
                }
                break;
            }

            // Handle match-associative carriers
            foreach (var massoc in carrier.GetCarriers(CarrierComponent.MatchAssociative))
            {
                if (_matchTable.TryGetValue(massoc, out result))
                {
                    pool = pool.Where(e => e.AssociatesWith(result));
                }
                break;
            }

            // Handle unique carriers
            foreach (var unique in carrier.GetCarriers(CarrierComponent.Unique))
            {
                HashSet <RantDictionaryEntry> usedSet;
                if (!_uniqueTable.TryGetValue(unique, out usedSet))
                {
                    usedSet = _uniqueTable[unique] = new HashSet <RantDictionaryEntry>();
                }

                pool = pool.Except(usedSet);
            }

            // Handle match-unique carriers
            foreach (var munique in carrier.GetCarriers(CarrierComponent.Unique))
            {
                if (_matchTable.TryGetValue(munique, out result))
                {
                    pool = pool.Where(e => e != result);
                }
            }

            // Handle relational carriers
            foreach (var relate in carrier.GetCarriers(CarrierComponent.Relational))
            {
                if (_assocTable.TryGetValue(relate, out result))
                {
                    pool = pool.Where(e => e.RelatesWith(result));
                }
            }

            // Handle match-relational carriers
            foreach (var relate in carrier.GetCarriers(CarrierComponent.MatchRelational))
            {
                if (_matchTable.TryGetValue(relate, out result))
                {
                    pool = pool.Where(e => e.RelatesWith(result));
                }
            }

            // Handle dissociative carriers
            foreach (var relate in carrier.GetCarriers(CarrierComponent.Dissociative))
            {
                if (_assocTable.TryGetValue(relate, out result))
                {
                    pool = pool.Where(e => !e.RelatesWith(result));
                }
            }

            // Handle match-dissociative carriers
            foreach (var relate in carrier.GetCarriers(CarrierComponent.MatchDissociative))
            {
                if (_matchTable.TryGetValue(relate, out result))
                {
                    pool = pool.Where(e => !e.RelatesWith(result));
                }
            }

            // Handle divergent carriers
            foreach (var diverge in carrier.GetCarriers(CarrierComponent.Divergent))
            {
                if (_assocTable.TryGetValue(diverge, out result))
                {
                    pool = pool.Where(e => e.DivergesFrom(result));
                }
            }

            // Handle match-divergent carriers
            foreach (var diverge in carrier.GetCarriers(CarrierComponent.MatchDivergent))
            {
                if (_matchTable.TryGetValue(diverge, out result))
                {
                    pool = pool.Where(e => e.DivergesFrom(result));
                }
            }

            result = pool.PickEntry(rng);

            // Handle rhyme carrier
            foreach (var rhyme in carrier.GetCarriers(CarrierComponent.Rhyme))
            {
                _ <RantDictionaryTerm, HashSet <RantDictionaryEntry> > rhymeState;
                if (!_rhymeTable.TryGetValue(rhyme, out rhymeState))
                {
                    result = pool
                             .Where(e => !Util.IsNullOrWhiteSpace(e.Terms[subtypeIndex].Pronunciation))
                             .PickEntry(rng);
                    if (result == null)
                    {
                        return(null);
                    }
                    _rhymeTable[rhyme] = _.Create(result.Terms[subtypeIndex], new HashSet <RantDictionaryEntry>(new[] { result }));
                    break;
                }
                result =
                    pool.Except(rhymeState.Item2)
                    .Where(e =>
                           !Util.IsNullOrWhiteSpace(e.Terms[subtypeIndex].Pronunciation) &&
                           _rhymer.Rhyme(rhymeState.Item1, e.Terms[subtypeIndex]))
                    .PickEntry(rng);

                if (result != null)
                {
                    rhymeState.Item2.Add(result);
                }
                break; // Ignore any extra rhyme carriers
            }

            if (result == null)
            {
                return(null);
            }

            foreach (var a in carrier.GetCarriers(CarrierComponent.Associative))
            {
                if (!_assocTable.ContainsKey(a))
                {
                    _assocTable[a] = result;
                }
            }

            foreach (var a in carrier.GetCarriers(CarrierComponent.Dissociative))
            {
                if (!_assocTable.ContainsKey(a))
                {
                    _assocTable[a] = result;
                }
            }

            foreach (var a in carrier.GetCarriers(CarrierComponent.Divergent))
            {
                if (!_assocTable.ContainsKey(a))
                {
                    _assocTable[a] = result;
                }
            }

            foreach (var a in carrier.GetCarriers(CarrierComponent.Relational))
            {
                if (!_assocTable.ContainsKey(a))
                {
                    _assocTable[a] = result;
                }
            }

            foreach (var unique in carrier.GetCarriers(CarrierComponent.Unique))
            {
                _uniqueTable[unique].Add(result);
            }

            foreach (var match in carrier.GetCarriers(CarrierComponent.Match))
            {
                _matchTable[match] = result;
                break;
            }

            return(result);
        }
        /// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path)
        {
            var name = "";
            string[] subtypes = { "default" };
            bool header = true;

            var scopedClassSet = new HashSet<string>();
            RantDictionaryEntry entry = null;
            var entries = new List<RantDictionaryEntry>();
            var entryStringes = new List<Stringe>();
            var types = new Dictionary<string, EntryTypeDef>();
            var hiddenClasses = new HashSet<string> { "nsfw" };

            foreach (var token in DicLexer.Tokenize(path, File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                    case DicTokenType.Directive:
                        {
                            var parts = VocabUtils.GetArgs(token.Value).ToArray();
                            if (!parts.Any()) continue;
                            var dirName = parts.First().ToLower();
                            var args = parts.Skip(1).ToArray();

                            switch (dirName)
                            {
                                case "name":    
                                    if (!header) LoadError(path, token, "The #name directive may only be used in the file header.");
                                    if (args.Length != 1) LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                                    if (!Util.ValidateName(args[0])) LoadError(path, token, $"Invalid #name value: '{args[1]}'");
                                    name = args[0].ToLower();
                                    break;
                                case "subs":
                                    if (!header) LoadError(path, token, "The #subs directive may only be used in the file header.");
                                    subtypes = args.Select(s => s.Trim().ToLower()).ToArray();
                                    break;
                                case "version": // Kept here for backwards-compatability
                                    if (!header) LoadError(path, token, "The #version directive may only be used in the file header.");
                                    break;
                                case "hidden":
                                    if (!header) LoadError(path, token, "The #hidden directive may only be used in the file header.");
                                    if (Util.ValidateName(args[0])) hiddenClasses.Add(args[0]);
                                    break;
                                    // Deprecated, remove in Rant 3
                                case "nsfw":
                                    scopedClassSet.Add("nsfw");
                                    break;
                                    // Deprecated, remove in Rant 3
                                case "sfw":
                                    scopedClassSet.Remove("nsfw");
                                    break;
                                case "class":
                                    {
                                        if (args.Length < 2) LoadError(path, token, "The #class directive expects an operation and at least one value.");
                                        switch (args[0].ToLower())
                                        {
                                            case "add":
                                                foreach (var cl in args.Skip(1))
                                                    scopedClassSet.Add(cl.ToLower());
                                                break;
                                            case "remove":
                                                foreach (var cl in args.Skip(1))
                                                    scopedClassSet.Remove(cl.ToLower());
                                                break;
                                        }
                                    }
                                    break;
                                case "type":
                                    {
                                        if (!header) LoadError(path, token, "The #type directive may only be used in the file header.");
                                        if (args.Length != 3) LoadError(path, token, "#type directive requires 3 arguments.");
                                        types.Add(args[0], new EntryTypeDef(args[0], args[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries),
                                            Util.IsNullOrWhiteSpace(args[2]) ? null : new EntryTypeDefFilter(args[2])));
                                    }
                                    break;
                            }
                        }
                        break;
                    case DicTokenType.Entry:
                        {
                            if (Util.IsNullOrWhiteSpace(name))
                                LoadError(path, token, "Missing table name before entry list.");
                            if (Util.IsNullOrWhiteSpace(token.Value))
                                LoadError(path, token, "Encountered empty entry.");
                            header = false;
                            entry = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet);
                            entries.Add(entry);
                            entryStringes.Add(token);
                        }
                        break;
                    case DicTokenType.DiffEntry:
                        {
                            if (Util.IsNullOrWhiteSpace(name))
                                LoadError(path, token, "Missing table name before entry list.");
                            if (Util.IsNullOrWhiteSpace(token.Value))
                                LoadError(path, token, "Encountered empty entry.");
                            header = false;
                            string first = null;
                            entry = new RantDictionaryEntry(token.Value.Split('/')
                                .Select((s, i) =>
                                {
                                    if (i > 0) return Diff.Mark(first, s);
                                    return first = s.Trim();
                                }).ToArray(), scopedClassSet);
                            entries.Add(entry);
                            entryStringes.Add(token);
                        }
                        break;
                    case DicTokenType.Property:
                        {
                            var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                            if (!parts.Any()) LoadError(path, token, "Empty property field.");
                            switch (parts[0].ToLower())
                            {
                                case "class":
                                    {
                                        if (parts.Length < 2) continue;
                                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                                        {
                                            bool opt = cl.EndsWith("?");
                                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                                        }
                                    }
                                    break;
                                case "weight":
                                    {
                                        if (parts.Length != 2) LoadError(path, token, "'weight' property expected a value.");
                                        int weight;
                                        if (!Int32.TryParse(parts[1], out weight))
                                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                                        entry.Weight = weight;
                                    }
                                    break;
                                case "pron":
                                    {
                                        if (parts.Length != 2) LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                                        var pron =
                                            parts[1].Split('/')
                                                .Select(s => s.Trim())
                                                .ToArray();
                                        if (subtypes.Length == pron.Length)
                                        {
                                            for (int i = 0; i < entry.Terms.Length; i++)
                                                entry.Terms[i].Pronunciation = pron[i];
                                        }
                                    }
                                    break;
                                default:
                                    {
                                        EntryTypeDef typeDef;
                                        if (!types.TryGetValue(parts[0], out typeDef))
                                            LoadError(path, token, $"Unknown property name '{parts[0]}'.");
                                        // Okay, it's a type.
                                        if (parts.Length != 2) LoadError(path, token, "Missing type value.");
                                        entry.AddClass(VocabUtils.GetString(parts[1]));
                                        if(!typeDef.IsValidValue(parts[1]))
                                            LoadError(path, token, $"'{parts[1]}' is not a valid value for type '{typeDef.Name}'.");
                                        break;
                                    }
                            }
                        }
                        break;
                }
            }

            if (types.Any())
            {
                var eEntries = entries.GetEnumerator();
                var eEntryStringes = entryStringes.GetEnumerator();
                while (eEntries.MoveNext() && eEntryStringes.MoveNext())
                {
                    foreach (var type in types.Values)
                    {
                        if (!type.Test(eEntries.Current))
                        {
                            // TODO: Find a way to output multiple non-fatal table load errors without making a gigantic exception message.
                            LoadError(path, eEntryStringes.Current, $"Entry '{eEntries.Current}' does not satisfy type '{type.Name}'.");
                        }
                    }
                }
            }

            return new RantDictionaryTable(name, subtypes, entries, hiddenClasses);
        }
示例#19
0
        internal override void DeserializeData(EasyReader reader)
        {
            this.Name          = reader.ReadString();
            this.Language      = reader.ReadString();
            this.TermsPerEntry = reader.ReadInt32();
            for (int i = 0; i < TermsPerEntry; i++)
            {
                foreach (var subtype in reader.ReadStringArray())
                {
                    AddSubtype(subtype, i);
                }
            }
            _hidden.AddRange(reader.ReadStringArray());

            int numEntries = reader.ReadInt32();

            for (int i = 0; i < numEntries; i++)
            {
                var terms = new RantDictionaryTerm[TermsPerEntry];
                for (int j = 0; j < TermsPerEntry; j++)
                {
                    var value      = reader.ReadString();
                    var pron       = reader.ReadString();
                    int valueSplit = reader.ReadInt32();
                    int pronSplit  = reader.ReadInt32();
                    terms[j] = new RantDictionaryTerm(value, pron, valueSplit, pronSplit);
                }
                float weight = reader.ReadSingle();
                var   entry  = new RantDictionaryEntry(terms)
                {
                    Weight = weight
                };

                foreach (var reqClass in reader.ReadStringArray())
                {
                    entry.AddClass(reqClass, false);
                }

                foreach (var optClass in reader.ReadStringArray())
                {
                    entry.AddClass(optClass, true);
                }

                int metaCount = reader.ReadInt32();

                for (int j = 0; j < metaCount; j++)
                {
                    bool isArray = reader.ReadBoolean();
                    var  key     = reader.ReadString();
                    if (isArray)
                    {
                        entry.SetMetadata(key, reader.ReadStringArray());
                    }
                    else
                    {
                        entry.SetMetadata(key, reader.ReadString());
                    }
                }

                AddEntry(entry);
            }
        }
示例#20
0
 /// <summary>
 /// Checks if the table contains the specified entry.
 /// </summary>
 /// <param name="entry">The entry to search for.</param>
 /// <returns>True if found, False if not.</returns>
 public bool ContainsEntry(RantDictionaryEntry entry)
 {
     return(_entriesHash.Contains(entry ?? throw new ArgumentNullException(nameof(entry))));
 }
示例#21
0
        public static bool DivergesFrom(this RantDictionaryEntry a, RantDictionaryEntry b)
        {
            if (a == null || b == null) return false;

            bool aNoneRequired = !a.GetRequiredClasses().Any();
            bool bNoneRequired = !b.GetRequiredClasses().Any();

            if (aNoneRequired && bNoneRequired) return true; // If both have no required classes, pass.

            // One or both have required classes.

            // Remove B optionals from A required.
            var aRequired = a.GetRequiredClasses().Except(b.GetOptionalClasses());
            // Remove A optionals from B required.
            var bRequired = b.GetRequiredClasses().Except(a.GetOptionalClasses());

            // Both should be either empty, or differ by at least one class.
            return aRequired.Except(bRequired).Any() || bRequired.Except(aRequired).Any();
        }
示例#22
0
 private bool DoTest(RantDictionaryEntry entry)
 {
     return _filterParts == null || _filterParts.All(f => entry.ContainsClass(f.Item1) == f.Item2);
 }
示例#23
0
 public bool Test(RantDictionaryEntry entry)
 {
     if (!EntryTypeDefFilter.Test(Filter, entry)) return true;
     return entry.GetClasses().Where(IsValidValue).Count() == 1;
 }
示例#24
0
        /// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <param name="nsfwFilter">Specifies whether to allow or disallow NSFW entries.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path, NsfwFilter nsfwFilter = NsfwFilter.Disallow)
        {
            var name    = "";
            var version = Version;

            string[] subtypes = { "default" };

            bool header = true;

            bool nsfw = false;

            var scopedClassSet = new HashSet <string>();

            RantDictionaryEntry entry = null;

            var entries = new List <RantDictionaryEntry>();

            foreach (var token in DicLexer.Tokenize(File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                case DicTokenType.Directive:
                {
                    var parts = token.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        continue;
                    }

                    switch (parts[0].ToLower())
                    {
                    case "name":
                        if (!header)
                        {
                            LoadError(path, token, "The #name directive may only be used in the file header.");
                        }
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                        }
                        if (!Util.ValidateName(parts[1]))
                        {
                            LoadError(path, token, "Invalid #name value: '\{parts[1]}'");
                        }
                        name = parts[1].ToLower();
                        break;

                    case "subs":
                        if (!header)
                        {
                            LoadError(path, token, "The #subs directive may only be used in the file header.");
                        }
                        subtypes = parts.Skip(1).Select(s => s.Trim().ToLower()).ToArray();
                        break;

                    case "version":
                        if (!header)
                        {
                            LoadError(path, token, "The #version directive may only be used in the file header.");
                        }
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "The #version directive requires a value.");
                        }
                        if (!int.TryParse(parts[1], out version))
                        {
                            LoadError(path, token, "Invalid version number '\{parts[1]}'");
                        }
                        if (version > Version)
                        {
                            LoadError(path, token, "Unsupported file version '\{version}'");
                        }
                        break;

                    case "nsfw":
                        nsfw = true;
                        break;

                    case "sfw":
                        nsfw = false;
                        break;

                    case "class":
                    {
                        if (parts.Length < 3)
                        {
                            LoadError(path, token, "The #class directive expects an operation and at least one value.");
                        }
                        switch (parts[1].ToLower())
                        {
                        case "add":
                            foreach (var cl in parts.Skip(2))
                            {
                                scopedClassSet.Add(cl.ToLower());
                            }
                            break;

                        case "remove":
                            foreach (var cl in parts.Skip(2))
                            {
                                scopedClassSet.Remove(cl.ToLower());
                            }
                            break;
                        }
                    }
                    break;
                    }
                }
                break;

                case DicTokenType.Entry:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing dictionary name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty dictionary entry.");
                    }
                    header = false;
                    entry  = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet, nsfw);
                    entries.Add(entry);
                }
                break;

                case DicTokenType.DiffEntry:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing dictionary name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty dictionary entry.");
                    }
                    header = false;
                    string first = null;
                    entry = new RantDictionaryEntry(token.Value.Split('/')
                                                    .Select((s, i) =>
                        {
                            if (i > 0)
                            {
                                return(Diff.Mark(first, s));
                            }
                            return(first = s.Trim());
                        }).ToArray(), scopedClassSet, nsfw);
                    entries.Add(entry);
                }
                break;

                case DicTokenType.Property:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        LoadError(path, token, "Empty property field.");
                    }
                    switch (parts[0].ToLower())
                    {
                    case "class":
                    {
                        if (parts.Length < 2)
                        {
                            continue;
                        }
                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            bool opt = cl.EndsWith("?");
                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                        }
                    }
                    break;

                    case "weight":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'weight' property expected a value.");
                        }
                        int weight;
                        if (!Int32.TryParse(parts[1], out weight))
                        {
                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                        }
                        entry.Weight = weight;
                    }
                    break;

                    case "pron":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                        }
                        var pron =
                            parts[1].Split('/')
                            .Select(s => s.Trim())
                            .ToArray();
                        if (subtypes.Length != pron.Length)
                        {
                            LoadError(path, token, "Pronunciation list length must match subtype count.");
                        }

                        for (int i = 0; i < entry.Terms.Length; i++)
                        {
                            entry.Terms[i].Pronunciation = pron[i];
                        }
                    }
                    break;
                    }
                }
                break;
                }
            }
            return(new RantDictionaryTable(name, subtypes, entries));
        }
示例#25
0
        /// <summary>
        /// Loads a package from the specified stream and returns it as a RantPackage object.
        /// </summary>
        /// <param name="source">The stream to load the package data from.</param>
        /// <returns></returns>
        public static RantPackage Load(Stream source)
        {
            using (var reader = new EasyReader(source))
            {
                if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != Magic)
                    throw new InvalidDataException("File is corrupt.");

                int numPatterns = reader.ReadInt32();
                int numTables = reader.ReadInt32();

                if (numPatterns < 0 || numTables < 0)
                    throw new InvalidDataException("File is corrupt.");

                var pkg = new RantPackage();

                // Patterns
                for (int i = 0; i < numPatterns; i++)
                {
                    var name = reader.ReadString();
                    var code = reader.ReadString();

                    pkg.AddPattern(new RantPattern(name, RantPatternSource.String, code));
                }

                // Tables
                for (int i = 0; i < numTables; i++)
                {
                    var name = reader.ReadString();
                    var subs = reader.ReadStringArray();
                    int numEntries = reader.ReadInt32();
                    var hiddenClasses = reader.ReadStringArray();
                    var entries = new RantDictionaryEntry[numEntries];

                    for (int j = 0; j < numEntries; j++)
                    {
                        int weight = reader.ReadInt32();
                        bool flags = reader.ReadBoolean(); // unused
                        int numTerms = reader.ReadInt32();
                        var terms = new RantDictionaryTerm[numTerms];

                        for (int k = 0; k < numTerms; k++)
                        {
                            var value = reader.ReadString();
                            var pron = reader.ReadString();
                            terms[k] = new RantDictionaryTerm(value, pron);
                        }

                        var classes = reader.ReadStringArray();

                        entries[j] = new RantDictionaryEntry(terms, classes, weight);
                    }

                    pkg.AddTable(new RantDictionaryTable(name, subs, entries, hiddenClasses));
                }

                return pkg;
            }
        }
示例#26
0
        /// <summary>
        /// Loads a table from the specified stream.
        /// </summary>
        /// <param name="origin">The origin of the stream. This will typically be a file path or package name.</param>
        /// <param name="stream">The stream to load the table from.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromStream(string origin, Stream stream)
        {
            string name               = null;                           // Stores the table name before final table construction
            int    termsPerEntry      = 0;                              // Stores the term count
            var    subtypes           = new Dictionary <string, int>(); // Stores subtypes before final table construction
            var    hidden             = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);
            RantDictionaryTable table = null;                           // The table object, constructed when first entry is found
            string l;                                                   // Current line string
            int    line = 0;                                            // Current line number
            int    len, i;                                              // Length and character index of current line
            bool   dummy = false;                                       // Determines if the next entry is a dummy entry
            string tId   = null;                                        // Template ID
            RantDictionaryEntry activeTemplate = null;                  // Current template
            var templates = new Dictionary <string, RantDictionaryEntry>();
            RantDictionaryEntry currentEntry = null;
            var autoClasses    = new HashSet <string>();
            var autoClassStack = new Stack <List <string> >();

            using (var reader = new StreamReader(stream))
            {
                while (!reader.EndOfStream)
                {
                    line++;

                    // Skip blank lines
                    if (Util.IsNullOrWhiteSpace(l = reader.ReadLine()))
                    {
                        continue;
                    }

                    // Update line info
                    len = l.Length;
                    i   = 0;

                    // Skip whitespace at the start of the line
                    while (i < len && char.IsWhiteSpace(l[i]))
                    {
                        i++;
                    }

                    switch (l[i++])
                    {
                    // Comments
                    case '#':
                        continue;

                    // Directive
                    case '@':
                    {
                        // Read directive name
                        int dPos = i;
                        if (!Tools.ReadDirectiveName(l, len, ref i, out string directiveName))
                        {
                            throw new RantTableLoadException(origin, line, dPos + 1, "err-table-missing-directive-name");
                        }

                        // Read arguments
                        var args = new List <Argument>();
                        while (Tools.ReadArg(origin, l, len, line, ref i, out Argument arg))
                        {
                            args.Add(arg);
                        }

                        switch (directiveName.ToLowerInvariant())
                        {
                        // Table name definition
                        case "name":
                        {
                            // Do not allow this to appear anywhere except at the top of the file
                            if (table != null)
                            {
                                throw new RantTableLoadException(origin, line, dPos + 1, "err-table-misplaced-header-directive");
                            }
                            // Do not allow multiple @name directives
                            if (name != null)
                            {
                                throw new RantTableLoadException(origin, line, dPos + 1, "err-table-multiple-names");
                            }
                            // One argument required
                            if (args.Count != 1)
                            {
                                throw new RantTableLoadException(origin, line, dPos + 1, "err-table-name-args");
                            }
                            // Must meet standard identifier requirements
                            if (!Util.ValidateName(args[0].Value))
                            {
                                throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-invalid-name", args[0].Value);
                            }
                            name = args[0].Value;
                            break;
                        }

                        // Subtype definition
                        case "sub":
                        {
                            // Do not allow this to appear anywhere except at the top of the file
                            if (table != null)
                            {
                                throw new RantTableLoadException(origin, line, dPos + 1, "err-table-misplaced-header-directive");
                            }
                            // @sub requires at least one argument
                            if (args.Count == 0)
                            {
                                throw new RantTableLoadException(origin, line, dPos + 1, "err-table-subtype-args");
                            }

                            // If the first argument is a number, use it as the subtype index.
                            if (Util.ParseInt(args[0].Value, out int termIndex))
                            {
                                // Disallow negative term indices
                                if (termIndex < 0)
                                {
                                    throw new RantTableLoadException(origin, line, dPos + 1, "err-table-sub-index-negative", termIndex);
                                }
                                // Requires at least one name
                                if (args.Count < 2)
                                {
                                    throw new RantTableLoadException(origin, line, dPos + 1, "err-table-sub-missing-name");
                                }
                                // If the index is outside the current term index range, increase the number.
                                if (termIndex >= termsPerEntry)
                                {
                                    termsPerEntry = termIndex + 1;
                                }
                                // Assign all following names to the index
                                for (int j = 1; j < args.Count; j++)
                                {
                                    // Validate subtype name
                                    if (!Util.ValidateName(args[j].Value))
                                    {
                                        throw new RantTableLoadException(origin, line, args[j].CharIndex + 1, "err-table-bad-subtype", args[j].Value);
                                    }
                                    subtypes[args[j].Value] = termIndex;
                                }
                            }
                            else
                            {
                                // Add to last index
                                termIndex = termsPerEntry++;
                                // Assign all following names to the index
                                foreach (var a in args)
                                {
                                    // Validate subtype name
                                    if (!Util.ValidateName(a.Value))
                                    {
                                        throw new RantTableLoadException(origin, line, a.CharIndex + 1, "err-table-bad-subtype", a.Value);
                                    }
                                    subtypes[a.Value] = termIndex;
                                }
                            }
                            break;
                        }

                        case "hide":
                            if (args.Count == 0)
                            {
                                break;
                            }
                            foreach (var a in args)
                            {
                                if (!Util.ValidateName(a.Value))
                                {
                                    throw new RantTableLoadException(origin, line, i, "err-table-invalid-class", a.Value);
                                }
                                hidden.Add(String.Intern(a.Value));
                            }
                            break;

                        case "dummy":
                            if (args.Count != 0)
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 0, args.Count);
                            }
                            dummy = true;
                            break;

                        case "id":
                            if (args.Count != 1)
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 1, args.Count);
                            }
                            if (!Util.ValidateName(args[0].Value))
                            {
                                throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-bad-template-id", args[0].Value);
                            }
                            tId = args[0].Value;
                            break;

                        case "using":
                            if (args.Count != 1)
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 1, args.Count);
                            }
                            if (!Util.ValidateName(args[0].Value))
                            {
                                throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-bad-template-id", args[0].Value);
                            }
                            if (!templates.TryGetValue(args[0].Value, out activeTemplate))
                            {
                                throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-template-not-found", args[0].Value);
                            }
                            break;

                        case "class":
                        {
                            var cList = new List <string>();
                            if (args.Count == 0)
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-args-expected", directiveName);
                            }
                            foreach (var cArg in args)
                            {
                                if (!Tools.ValidateClassName(cArg.Value))
                                {
                                    throw new RantTableLoadException(origin, line, cArg.CharIndex + 1, "err-table-invalid-class", cArg.Value);
                                }
                                cList.Add(cArg.Value);
                                autoClasses.Add(cArg.Value);
                            }
                            autoClassStack.Push(cList);
                            break;
                        }

                        case "endclass":
                        {
                            if (args.Count == 0)
                            {
                                if (autoClassStack.Count > 0)
                                {
                                    foreach (string cName in autoClassStack.Pop())
                                    {
                                        autoClasses.Remove(cName);
                                    }
                                }
                            }
                            break;
                        }
                        }
                        break;
                    }

                    // Entry
                    case '>':
                        Tools.ConstructTable(origin, name, subtypes, ref termsPerEntry, ref table);
                        Tools.ReadTerms(origin, l, len, line, ref i, table, activeTemplate, templates, out currentEntry);
                        if (!dummy)
                        {
                            table.AddEntry(currentEntry);
                        }
                        foreach (string autoClass in autoClasses)
                        {
                            currentEntry.AddClass(autoClass);
                        }
                        if (tId != null)
                        {
                            templates[tId] = currentEntry;
                            tId            = null;
                        }
                        dummy          = false;
                        activeTemplate = null;
                        break;

                    // Property
                    case '-':
                    {
                        Tools.ConstructTable(origin, name, subtypes, ref termsPerEntry, ref table);
                        Tools.SkipSpace(l, len, ref i);

                        // Read property name
                        int dPos = i;
                        if (!Tools.ReadDirectiveName(l, len, ref i, out string propName))
                        {
                            throw new RantTableLoadException(origin, line, dPos + 1, "err-table-missing-property-name");
                        }

                        // Read arguments
                        var args = new List <Argument>();
                        while (Tools.ReadArg(origin, l, len, line, ref i, out Argument arg))
                        {
                            args.Add(arg);
                        }

                        // No args? Skip it.
                        if (args.Count == 0)
                        {
                            continue;
                        }

                        switch (propName.ToLowerInvariant())
                        {
                        case "class":
                            foreach (var cArg in args)
                            {
                                if (!Tools.ValidateClassName(cArg.Value))
                                {
                                    throw new RantTableLoadException(origin, line, cArg.CharIndex + 1, "err-table-invalid-class", cArg.Value);
                                }
                                currentEntry.AddClass(cArg.Value);
                            }
                            break;

                        case "weight":
                        {
                            if (!float.TryParse(args[0].Value, out float weight) || weight <= 0)
                            {
                                throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-invalid-weight", args[0].Value);
                            }
                            currentEntry.Weight   = weight;
                            table.EnableWeighting = true;
                            break;
                        }

                        case "pron":
                            if (args.Count != table.TermsPerEntry)
                            {
                                continue;
                            }
                            for (int j = 0; j < currentEntry.TermCount; j++)
                            {
                                currentEntry[j].Pronunciation = args[j].Value;
                            }
                            break;

                        default:
                            if (args.Count == 1)
                            {
                                currentEntry.SetMetadata(propName, args[0].Value);
                            }
                            else
                            {
                                currentEntry.SetMetadata(propName, args.Select(a => a.Value).ToArray());
                            }
                            break;
                        }
                        break;
                    }
                    }
                }
            }

            // Add hidden classes
            foreach (string hc in hidden)
            {
                table.HideClass(hc);
            }

            table.RebuildCache();

            return(table);
        }
示例#27
0
            public static void ReadTerms(string origin, string str, int len, int line, ref int i,
                                         RantDictionaryTable table, RantDictionaryEntry activeTemplate, Dictionary <string, RantDictionaryEntry> templates, out RantDictionaryEntry result)
            {
                SkipSpace(str, len, ref i);
                int  t      = 0;
                var  terms  = new RantDictionaryTerm[table.TermsPerEntry];
                int  split  = -1;
                char c      = '\0';
                var  buffer = new StringBuilder();
                var  white  = new StringBuilder();

                while (i < len)
                {
                    switch (c = str[i++])
                    {
                    // Inline comment
                    case '#':
                        goto done;

                    // Phrasal split operator
                    case '+':
                        if (split > -1)
                        {
                            throw new RantTableLoadException(origin, line, i, "err-table-multiple-splits");
                        }
                        white.Length = 0;
                        split        = buffer.Length;
                        SkipSpace(str, len, ref i);
                        break;

                    // Term reference
                    case '[':
                    {
                        SkipSpace(str, len, ref i);
                        if (i >= len)
                        {
                            throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference");
                        }
                        int start = i;
                        if (white.Length > 0)
                        {
                            buffer.Append(white);
                            white.Length = 0;
                        }
                        switch (str[i++])
                        {
                        // Current term from active template
                        case ']':
                            if (t == -1)
                            {
                                throw new RantTableLoadException(origin, line, start + 1, "err-table-no-template");
                            }
                            buffer.Append(activeTemplate[t].Value);
                            break;

                        // Custom term from active template
                        case '.':
                        {
                            if (activeTemplate == null)
                            {
                                throw new RantTableLoadException(origin, line, start + 1, "err-table-no-template");
                            }
                            while (i < len && IsValidSubtypeChar(str[i]))
                            {
                                i++;                                                   // Read subtype name
                            }
                            if (str[i] != ']')
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference");
                            }
                            string subName = str.Substring(start + 1, i - start - 1);
                            if (subName.Length == 0)
                            {
                                throw new RantTableLoadException(origin, line, start + 1, "err-table-empty-subtype-reference");
                            }
                            int templateSubIndex = table.GetSubtypeIndex(subName);
                            if (templateSubIndex == -1)
                            {
                                throw new RantTableLoadException(origin, line, start + 1, "err-table-nonexistent-subtype", subName);
                            }

                            // Add term value to buffer
                            buffer.Append(activeTemplate[templateSubIndex].Value);
                            i++;         // Skip past closing bracket
                            break;
                        }

                        // It is probably a reference to another entry, let's see.
                        default:
                        {
                            while (i < len && IsValidSubtypeChar(str[i]) || str[i] == '.')
                            {
                                i++;
                            }
                            if (str[i] != ']')
                            {
                                throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference");
                            }
                            var id = str.Substring(start, i - start).Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                            switch (id.Length)
                            {
                            // It's just a template ID.
                            case 1:
                            {
                                if (!templates.TryGetValue(id[0], out RantDictionaryEntry entry))
                                {
                                    throw new RantTableLoadException(origin, line, start + 1, "err-table-entry-not-found");
                                }
                                // Append term value to buffer
                                buffer.Append(entry[t].Value);
                                break;
                            }

                            // Template ID and custom subtype
                            case 2:
                            {
                                if (!templates.TryGetValue(id[0], out RantDictionaryEntry entry))
                                {
                                    throw new RantTableLoadException(origin, line, start + 1, "err-table-entry-not-found");
                                }
                                int templateSubIndex = table.GetSubtypeIndex(id[1]);
                                if (templateSubIndex == -1 || templateSubIndex >= table.TermsPerEntry)
                                {
                                    throw new RantTableLoadException(origin, line, start + 1, "err-table-nonexistent-subtype", id[1]);
                                }
                                buffer.Append(entry[templateSubIndex].Value);
                                break;
                            }

                            // ???
                            default:
                                throw new RantTableLoadException(origin, line, start + 1, "err-table-invalid-term-reference");
                            }

                            i++;         // Skip past closing bracket
                            break;
                        }
                        }
                        break;
                    }

                    case '\\':
                    {
                        if (white.Length > 0)
                        {
                            buffer.Append(white);
                            white.Length = 0;
                        }
                        switch (c = str[i++])
                        {
                        case 'n':
                            buffer.Append('\n');
                            continue;

                        case 'r':
                            buffer.Append('\r');
                            continue;

                        case 't':
                            buffer.Append('\t');
                            continue;

                        case 'v':
                            buffer.Append('\v');
                            continue;

                        case 'f':
                            buffer.Append('\f');
                            continue;

                        case 'b':
                            buffer.Append('\b');
                            continue;

                        case 's':
                            buffer.Append(' ');
                            continue;

                        case 'u':
                        {
                            if (i + 4 > len)
                            {
                                throw new RantTableLoadException(origin, line, i + 1, "err-table-incomplete-escape");
                            }
                            if (!ushort.TryParse(str.Substring(i, 4), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out ushort codePoint))
                            {
                                throw new RantTableLoadException(origin, line, i + 1, "err-table-unrecognized-codepoint");
                            }
                            buffer.Append((char)codePoint);
                            i += 4;
                            continue;
                        }

                        case 'U':
                        {
                            if (i + 8 > len)
                            {
                                throw new RantTableLoadException(origin, line, i + 1, "err-table-incomplete-escape");
                            }
                            if (!Util.TryParseSurrogatePair(str.Substring(i, 8), out char high, out char low))
                            {
                                throw new RantTableLoadException(origin, line, i + 1, "err-table-unrecognized-codepoint");
                            }
                            buffer.Append(high).Append(low);
                            i += 8;
                            continue;
                        }

                        default:
                            buffer.Append(c);
                            continue;
                        }
                        continue;
                    }

                    case ',':
                        if (t >= terms.Length)
                        {
                            throw new RantTableLoadException(origin, line, i, "err-table-too-many-terms", terms.Length, t);
                        }
                        terms[t++]    = new RantDictionaryTerm(buffer.ToString(), split);
                        buffer.Length = 0;
                        white.Length  = 0;
                        split         = -1;
                        SkipSpace(str, len, ref i);
                        break;

                    default:
                        if (char.IsWhiteSpace(c))
                        {
                            white.Append(c);
                        }
                        else
                        {
                            if (white.Length > 0)
                            {
                                buffer.Append(white);
                                white.Length = 0;
                            }
                            buffer.Append(c);
                        }
                        continue;
                    }
                }

done:

                if (t != terms.Length - 1)
                {
                    throw new RantTableLoadException(origin, line, i, "err-table-too-few-terms", terms.Length, t + 1);
                }

                terms[t] = new RantDictionaryTerm(buffer.ToString());

                result = new RantDictionaryEntry(terms);

                // Add classes from template
                if (activeTemplate != null)
                {
                    foreach (string cl in activeTemplate.GetRequiredClasses())
                    {
                        result.AddClass(cl, false);
                    }
                    foreach (string cl in activeTemplate.GetOptionalClasses())
                    {
                        result.AddClass(cl, true);
                    }
                }
            }
 // Returns the classes of an object, but optional classes are postfixed with ?
 private static IEnumerable<string> GetClassesForExport(RantDictionaryEntry entry)
 {
     return entry.GetRequiredClasses().Concat(entry.GetOptionalClasses().Select(x => x + "?"));
 }