Esempio n. 1
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);
        }