Ejemplo n.º 1
0
        /// <summary>
        /// Extract an integral number from 'src' (basically 'strtol')
        /// Expects 'src' to point to a string of the following form:
        /// [delim] [{+|–}][0[{x|X|b|B}]][digits]
        /// The first character that does not fit this form stops the scan.
        /// If 'radix' is between 2 and 36, then it is used as the base of the number.
        /// If 'radix' is 0, the initial characters of the string are used to determine the base.
        /// If the first character is 0 and the second character is not 'x' or 'X', the string is interpreted as an octal integer;
        /// otherwise, it is interpreted as a decimal number. If the first character is '0' and the second character is 'x' or 'X',
        /// the string is interpreted as a hexadecimal integer. If the first character is '1' through '9', the string is interpreted
        /// as a decimal integer. The letters 'a' through 'z' (or 'A' through 'Z') are assigned the values 10 through 35; only letters
        /// whose assigned values are less than 'radix' are permitted.</summary>
        public static bool Int(out long intg, int radix, Src src, string?delim = null)
        {
            intg = 0;

            if (!BufferNumber(src, ref radix, out var len, ENumType.Int, delim))
            {
                return(false);
            }

            try
            {
                var str = src.Buffer.ToString(0, len).ToLower();
                str = str.TrimEnd('u', 'l');
                if (radix == 2 && str.StartsWith("0b"))
                {
                    str = str.Remove(0, 2);
                }
                if (radix == 8 && str.StartsWith("0o"))
                {
                    str = str.Remove(0, 2);
                }
                if (radix == 16 && str.StartsWith("0x"))
                {
                    str = str.Remove(0, 2);
                }
                intg = Convert.ToInt64(str, radix);
                src += len;
                return(true);
            }
            catch { return(false); }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Construct a preprocessor macro of the form: 'TAG(p0,p1,..,pn)' expansion
        /// from a stream of characters. Stops at the first non-escaped new line</summary>
        public Macro(Src src, Loc loc)
            : this(null, null, null, loc)
        {
            // Extract the tag and find it's hash code
            m_tag = ReadTag(src, loc);

            // Hash the tag
            //	m_hash = Hash(m_tag);

            // Extract the optional parameters
            if (src == '(')
            {
                ReadParams(src, m_params, loc, true);
            }

            // Trim whitespace from before the expansion text
            for (; src != 0 && Str_.IsLineSpace(src); ++src)
            {
            }

            // Extract the expansion and trim all leading and following whitespace
            if (!Extract.Line(out var expansion, src, true))
            {
                throw new ScriptException(EResult.InvalidMacroDefinition, loc, "invalid macro expansion");
            }

            m_expansion = expansion.Trim();
        }
Ejemplo n.º 3
0
        /// <summary>Extract multi-part identifiers from 'src' incrementing 'src'. E.g. House.Room.Item</summary>
        public static bool Identifiers(out string[] ids, Src src, char sep = '.', string?delim = null)
        {
            ids = Array.Empty <string>();
            var idents = new List <string>();

            for (; ;)
            {
                if (idents.Count != 0)
                {
                    if (src == sep)
                    {
                        ++src;
                    }
                    else
                    {
                        break;
                    }
                }
                if (!Identifier(out var id, src, delim))
                {
                    return(false);
                }
                idents.Add(id);
            }
            ids = idents.ToArray();
            return(true);
        }
Ejemplo n.º 4
0
 public Reader(Src src, bool case_sensitive = false, IIncludeHandler?inc = null, IMacroHandler?mac = null, EmbeddedCodeFactory?emb = null)
 {
     Src           = new Preprocessor(src, inc, mac, emb);
     Delimiters    = " \t\r\n\v,;";
     LastKeyword   = string.Empty;
     CaseSensitive = case_sensitive;
 }
Ejemplo n.º 5
0
 protected Src(Src wrapped)
 {
     m_src  = wrapped;
     m_loc  = new Loc();
     Buffer = new StringBuilder(256);
     Limit  = long.MaxValue;
 }
Ejemplo n.º 6
0
        /// <summary>
        /// Extract a boolean from 'src'
        /// Expects 'src' to point to a string of the following form:
        /// [delim]{0|1|true|false}
        /// The first character that does not fit this form stops the scan.
        /// '0','1' must be followed by a non-identifier character
        /// 'true', 'false' can have any case</summary>
        public static bool Bool(out bool bool_, Src src, string?delim = null)
        {
            bool_ = false;
            delim ??= DefaultDelimiters;

            // Find the first non-delimiter
            if (!AdvanceToNonDelim(src, delim))
            {
                return(false);
            }

            // Extract the boolean
            switch (char.ToLower(src))
            {
            default: return(false);

            case '0': bool_ = false; return(!Str_.IsIdentifier(++src, false));

            case '1': bool_ = true; return(!Str_.IsIdentifier(++src, false));

            case 't': bool_ = true; return(char.ToLower(++src) == 'r' && char.ToLower(++src) == 'u' && char.ToLower(++src) == 'e' && !Str_.IsIdentifier(++src, false));

            case 'f': bool_ = false; return(char.ToLower(++src) == 'a' && char.ToLower(++src) == 'l' && char.ToLower(++src) == 's' && char.ToLower(++src) == 'e' && !Str_.IsIdentifier(++src, false));
            }
        }
Ejemplo n.º 7
0
 /// <summary>Return a macro tag from 'src' (or fail)</summary>
 private string ReadTag(Src src, Loc loc)
 {
     if (Extract.Identifier(out var tag, src))
     {
         return(tag);
     }
     throw new ScriptException(EResult.InvalidIdentifier, loc, "invalid macro name");
 }
Ejemplo n.º 8
0
 public TemplateReplacer(Src src, string pattern, SubstituteFunction subst, int chunk_size = 4096)
 {
     Src            = new SrcStack(src);
     Substitute     = subst;
     Pattern        = new Regex(pattern);
     ChunkSize      = chunk_size;
     OutputLocation = new Loc();
     m_match_ofs    = 0;
 }
Ejemplo n.º 9
0
        /// <summary>Push a script source onto the stack</summary>
        public void Push(Src src)
        {
            // The stack takes ownership of 'src' and calls dispose on it when popped.
            if (src == null)
            {
                throw new ArgumentNullException("src", "Null character source provided");
            }

            Stack.Push(src);
        }
Ejemplo n.º 10
0
 public static bool IntArray(out long[] intg, int count, int radix, Src src, string?delim = null)
 {
     intg = new long[count];
     for (int i = 0; i != count; ++i)
     {
         if (!Int(out intg[i], radix, src, delim))
         {
             return(false);
         }
     }
     return(true);
 }
Ejemplo n.º 11
0
 public static bool BoolArray(out bool[] bool_, int count, Src src, string?delim = null)
 {
     bool_ = new bool[count];
     for (int i = 0; i != count; ++i)
     {
         if (!Bool(out bool_[i], src, delim))
         {
             return(false);
         }
     }
     return(true);
 }
Ejemplo n.º 12
0
 /// <summary>Extract a contiguous block of characters up to (and possibly including) a new line character</summary>
 public static bool Line(out string line, Src src, bool inc_cr, string?newline = null)
 {
     newline ??= "\n";
     BufferWhile(src, (s, i) => !newline.Contains(s[i]) ? 1 : 0, 0, out var len);
     if (inc_cr && src[len] != 0)
     {
         len += newline.Length; src.ReadAhead(len);
     }
     line = src.Buffer.ToString(0, len);
     src += len;
     return(true);
 }
Ejemplo n.º 13
0
 public static bool RealArray(out double[] real, int count, Src src, string?delim = null)
 {
     real = new double[count];
     for (int i = 0; i != count; ++i)
     {
         if (!Real(out real[i], src, delim))
         {
             return(false);
         }
     }
     return(true);
 }
Ejemplo n.º 14
0
        /// <summary>
        /// Reads the next character from the input stream. The returned value is
        /// -1 if no further characters are available.</summary>
        public override int Read()
        {
            var ch = Peek();

            if (ch == -1)
            {
                return(-1);
            }

            Src.Next();
            --m_match_ofs;
            OutputLocation.inc((char)ch);
            return(ch);
        }
Ejemplo n.º 15
0
        /// <summary>Extract a contiguous block of non-delimiter characters from 'src'</summary>
        public static bool Token(out string token, Src src, string?delim = null)
        {
            token = string.Empty;
            delim ??= DefaultDelimiters;

            // Find the first non-delimiter
            if (!AdvanceToNonDelim(src, delim))
            {
                return(false);
            }

            // Copy up to the next delimiter
            BufferWhile(src, (s, i) => !delim.Contains(s[i]) ? 1 : 0, 0, out var len);
            token = src.Buffer.ToString(0, len);
            src  += len;
            return(true);
        }
Ejemplo n.º 16
0
        /// <summary>
        /// Extract a floating point number from 'src'
        /// Expects 'src' to point to a string of the following form:
        /// [delim] [{+|-}][digits][.digits][{d|D|e|E}[{+|-}]digits]
        /// The first character that does not fit this form stops the scan.
        /// If no digits appear before the '.' character, at least one must appear after the '.' character.
        /// The decimal digits can be followed by an exponent, which consists of an introductory letter (d, D, e, or E) and an optionally signed integer.
        /// If neither an exponent part nor a '.' character appears, a '.' character is assumed to follow the last digit in the string.</summary>
        public static bool Real(out double real, Src src, string?delim = null)
        {
            real = 0;

            int radix = 10;

            if (!BufferNumber(src, ref radix, out var len, ENumType.FP, delim))
            {
                return(false);
            }

            try
            {
                var str = src.Buffer.ToString(0, len).ToLower();
                str  = str.TrimEnd('f');
                real = Convert.ToDouble(str);
                src += len;
                return(true);
            }
            catch { return(false); }
        }
Ejemplo n.º 17
0
        /// <summary>Extract a contiguous block of identifier characters from 'src' incrementing 'src'</summary>
        public static bool Identifier(out string id, Src src, string?delim = null)
        {
            id = string.Empty;
            delim ??= DefaultDelimiters;

            // Find the first non-delimiter
            if (!AdvanceToNonDelim(src, delim))
            {
                return(false);
            }

            // If the first non-delimiter is not a valid identifier character, then we can't extract an identifier
            if (!Str_.IsIdentifier(src, true))
            {
                return(false);
            }

            // Copy up to the first non-identifier character
            BufferWhile(src, (s, i) => Str_.IsIdentifier(s[i], false) ? 1 : 0, 1, out var len);
            id   = src.Buffer.ToString(0, len);
            src += len;
            return(true);
        }
Ejemplo n.º 18
0
        /// <summary>Expands template mark-up files</summary>
        public static string Markup(Src src, string current_dir)
        {
            // Supported substitutions:
            // <!--#include file="filepath"-->
            //   Substitutes the content of the file 'filepath' at the declared location.
            //   'filepath' can be relative to directory of the template file, or a full path
            // <!--#var name="count" file="filepath" value="const int Count = (?<value>\d+);"-->
            //   Declares a variable for later use. Applies a regex match to the contents of 'filepath' to get a definition for 'value'
            //   'filepath' can be relative to directory of the template file, or a full path
            // <!--#value name="count"-->
            //   Substitute a previously defined variable called 'count'
            // <!--#image file="filepath"-->
            //   Substitute a base64 image file 'filepath'

            var variables = new Dictionary <string, string>();

            // General form: [optional leading whitespace]<!--#command key="value" key="value"... -->
            // The pattern matches leading white space which is then inserted before every line in the substituted result.
            // This means includes on lines of the own are correctly tabbed, and <!--inline--> substitutions are also correct
            var result = TemplateReplacer.Process(src, @"(?<indent>[ \t]*)<!--#(?<cmd>\w+)\s+(?<kv>.*?)-->", (tr, match) =>
            {
                var indent = match.Result("${indent}");
                var cmd    = match.Result("${cmd}");
                var kv     = match.Result("${kv}");                          // list of key="value" pairs
                Match m;
                switch (cmd)
                {
                default:
                    {
                        throw new Exception(string.Format("Unknown template command: '{0}' ({1})", cmd, match.ToString()));
                    }

                case "include":
                    #region include
                    {
                        const string expected_form = "<!--#include file=\"filepath\"-->";

                        // Read the include filepath and check it exists
                        m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid include: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var fpath = m.Result("${file}");

                        fpath = Path.IsPathRooted(fpath) ? fpath : Path.Combine(current_dir, fpath);
                        if (!File.Exists(fpath))
                        {
                            throw new FileNotFoundException(string.Format("File reference not found: {0}", fpath), fpath);
                        }

                        tr.PushSource(new AddIndents(new FileSrc(fpath), indent, true));
                        return(string.Empty);
                    }

                    #endregion
                case "var":
                    #region var
                    {
                        const string expected_form = "<!--#var name=\"variable_name\" file=\"filepath\" value=\"regex_pattern_defining_value\"-->";

                        // Read the name of the variable
                        m = Regex.Match(kv, @".*name=""(?<name>\w+)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'name' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var name = m.Result("${name}");

                        // Read the source filepath
                        m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var filepath = m.Result("${file}");
                        filepath     = Path.IsPathRooted(filepath) ? filepath : Path.Combine(current_dir, filepath);
                        if (!File.Exists(filepath))
                        {
                            throw new FileNotFoundException(string.Format("File reference not found: {0}", filepath), filepath);
                        }

                        // Read the regex pattern that defines 'value'
                        m = Regex.Match(kv, @".*value=""(?<pattern>.*?)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'value' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var pat = m.Result("${pattern}");
                        Regex pattern;
                        try { pattern = new Regex(pat, RegexOptions.Compiled); }
                        catch (Exception ex) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\n'value' field is not a valid Regex expression.\nExpected form: {1}", match.ToString(), expected_form), ex); }

                        // Use the pattern to get the value for the variable
                        using (var sr = new StreamReader(filepath, Encoding.UTF8, true, 65536))
                            for (string?line; (line = sr.ReadLine()) != null && !(m = pattern.Match(line)).Success;)
                            {
                            }
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid variable declaration: '{0}'.\n'value' regex expression did not find a match in {2}.\nExpected form: {1}", match.ToString(), expected_form, filepath));
                        }
                        var value = m.Result("${value}");

                        // Save the name/value pair
                        variables[name] = value;
                        return(string.Empty);
                    }

                    #endregion
                case "value":
                    #region value
                    {
                        const string expected_form = "<!--#value name=\"variable_name\"-->";

                        // Read the name of the variable
                        m = Regex.Match(kv, @".*name=""(?<name>\w+)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid value declaration: '{0}'.\nCould not match 'name' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var name = m.Result("${name}");

                        // Lookup the value for the variable
                        if (!variables.TryGetValue(name, out var value))
                        {
                            throw new Exception(string.Format("Invalid value declaration: '{0}'.\nVariable with 'name' {2} is not defined.\nExpected form: {1}", match.ToString(), expected_form, name));
                        }
                        return(indent + value);
                    }

                    #endregion
                case "image":
                    #region image
                    {
                        const string expected_form = "<!--#image file=\"filepath\"-->";

                        // Read the filename
                        m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*");
                        if (!m.Success)
                        {
                            throw new Exception(string.Format("Invalid image declaration: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form));
                        }
                        var fpath = m.Result("${file}");

                        fpath = Path.IsPathRooted(fpath) ? fpath : Path.Combine(current_dir, fpath);
                        if (!File.Exists(fpath))
                        {
                            throw new FileNotFoundException(string.Format("File reference not found: {0}", fpath), fpath);
                        }

                        // Determine the image type
                        var extn = (Path.GetExtension(fpath) ?? string.Empty).ToLowerInvariant();
                        if (string.IsNullOrEmpty(extn))
                        {
                            throw new Exception(string.Format("Could not determine image file format from path '{0}'", fpath));
                        }

                        // Read the image file
                        var img = File.ReadAllBytes(fpath);
                        switch (extn)
                        {
                        default: throw new Exception(string.Format("Unsupported image format: {0}", extn));

                        case ".png": return(string.Format("data:image/png;base64,{0}", Convert.ToBase64String(img)));

                        case ".jpg": return(string.Format("data:image/jpg;base64,{0}", Convert.ToBase64String(img)));
                        }
                    }
                    #endregion
                }
            });

            return(result);
        }
Ejemplo n.º 19
0
 /// <summary>Process a string template</summary>
 public static string Process(Src template, string pattern, SubstituteFunction subst, int chunk_size = 4096)
 {
     using var tr = new TemplateReplacer(template, pattern, subst, chunk_size);
     return(tr.ReadToEnd());
 }
Ejemplo n.º 20
0
 /// <summary>
 /// Extract a quoted (") string.
 /// if 'escape' is not 0, it is treated as the escape character
 /// if 'quote' is not null, it is treated as the accepted quote characters</summary>
 public static bool String(out string str, Src src, string?delim = null)
 {
     return(String(out str, src, '\0', null, delim));
 }
Ejemplo n.º 21
0
 protected virtual void Dispose(bool _)
 {
     Src.Dispose();
 }
Ejemplo n.º 22
0
 /// <summary>
 /// Extract a number of unknown format from 'src'
 /// Expects 'src' to point to a string containing any allowable number format (int or real).</summary>
 public static bool Number <Num>(out Num num, Src src, int radix = 0, string?delim = null)
     where Num : struct
 {
     num = default !;
Ejemplo n.º 23
0
 public Preprocessor(Src src, IIncludeHandler?inc = null, IMacroHandler?mac = null, EmbeddedCodeFactory?emb = null)
     : this(inc, mac, emb)
 {
     Stack.Push(src);
 }
Ejemplo n.º 24
0
 public StripComments(Src src, InLiteral.EFlags literal_flags = InLiteral.EFlags.Escaped | InLiteral.EFlags.SingleLineStrings, InComment.Patterns?comment_patterns = null)
     : base(src)
 {
     m_com = new InComment(comment_patterns ?? new InComment.Patterns(), literal_flags);
 }
Ejemplo n.º 25
0
 /// <summary>Expands template html files</summary>
 public static string Html(Src src, string current_dir) => Markup(src, current_dir);
Ejemplo n.º 26
0
 protected override void Dispose(bool disposing)
 {
     Src.Dispose();
     base.Dispose(disposing);
 }
Ejemplo n.º 27
0
        public static bool String(out string str, Src src, char escape, string?quotes = null, string?delim = null)
        {
            str = string.Empty;
            delim ??= DefaultDelimiters;

            // Set the accepted quote characters
            quotes ??= "\"\'";

            // Find the first non-delimiter
            if (!AdvanceToNonDelim(src, delim))
            {
                return(false);
            }

            // If the next character is not an acceptable quote, then this isn't a string
            char quote = src;

            if (quotes.Contains(quote))
            {
                ++src;
            }
            else
            {
                return(false);
            }

            // Copy the string
            var sb = new StringBuilder();

            if (escape != 0)
            {
                // Copy to the closing quote, allowing for the escape character
                for (; src != 0 && src != quote; ++src)
                {
                    if (src == escape)
                    {
                        switch (++src)
                        {
                        default: break;

                        case 'a':  sb.Append('\a'); break;

                        case 'b':  sb.Append('\b'); break;

                        case 'f':  sb.Append('\f'); break;

                        case 'n':  sb.Append('\n'); break;

                        case 'r':  sb.Append('\r'); break;

                        case 't':  sb.Append('\t'); break;

                        case 'v':  sb.Append('\v'); break;

                        case '\'': sb.Append('\''); break;

                        case '\"': sb.Append('\"'); break;

                        case '\\': sb.Append('\\'); break;

                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        {
                            // ASCII character in octal
                            var oct = new char[8]; var i = 0;
                            for (; i != oct.Length && Str_.IsOctDigit(src); ++i, ++src)
                            {
                                oct[i] = src;
                            }
                            sb.Append((char)Convert.ToInt32(new string(oct, 0, i), 8));
                            break;
                        }

                        case 'x':
                        {
                            // ASCII or UNICODE character in hex
                            var hex = new char[8]; var i = 0;
                            for (; i != hex.Length && Str_.IsHexDigit(src); ++i, ++src)
                            {
                                hex[i] = src;
                            }
                            sb.Append((char)Convert.ToInt32(new string(hex, 0, i), 16));
                            break;
                        }
                        }
                    }
                    else
                    {
                        sb.Append(src);
                    }
                }
            }
            else
            {
                // Copy to the next quote
                for (; src != 0 && src != quote; ++src)
                {
                    sb.Append(src);
                }
            }

            // If the string doesn't end with a quote, then it's not a valid string
            if (src == quote)
            {
                ++src;
            }
            else
            {
                return(false);
            }
            str = sb.ToString();
            return(true);
        }
Ejemplo n.º 28
0
 /// <summary>Push a new stream onto the source stack</summary>
 public void PushSource(Src src)
 {
     Src.Push(src);
 }
Ejemplo n.º 29
0
        /// <summary>
        /// Extract a comma separated parameter list of the form '(p0,p1,..,pn)'
        /// If 'identifiers' is true then the parameters are expected to be identifiers.
        /// If not, then anything delimited by commas is accepted. 'identifiers' == true is used when
        /// reading the definition of the macro, 'identifiers' == false is used when expanding an instance.
        /// If an empty parameter list is given, i.e. "()" then 'args' is returned containing one blank parameter.
        /// Returns true if the macro does not take parameters or the correct number of parameters where given,
        /// false if the macro takes parameters but none were given. Basically, 'false' means, don't treat
        /// this macro as matching because no params were given. If false is returned the buffer will
        /// contain anything read during this method.</summary>
        private bool ReadParams(Src buf, List <string> args, Loc location, bool identifiers)
        {
            // Buffer up to the first non-whitespace character
            // If no parameters are given, then the macro doesn't match
            if (!identifiers && args.Count != 0)
            {
                int i = 0; for (; Str_.IsWhiteSpace(buf[i]); ++i)
                {
                }
                if (buf[i] != '(')
                {
                    return(false);
                }
                buf += i;
            }

            // If we're not reading the parameter names for a macro definition
            // and the macro takes no parameters, then ReadParams is a no-op
            if (!identifiers && m_params.Count == 0)
            {
                return(true);
            }

            // Capture the strings between commas as the parameters
            for (++buf; buf != ')'; buf += buf != ')' ? 1 : 0)
            {
                // Read parameter names for a macro definition
                if (identifiers)
                {
                    if (!Extract.Identifier(out var arg, buf))
                    {
                        throw new ScriptException(EResult.InvalidIdentifier, location, "invalid macro identifier");
                    }
                    args.Add(arg);
                }

                // Read parameters being passed to the macro
                else
                {
                    var arg = new StringBuilder();
                    for (int nest = 0; (buf != ',' && buf != ')') || nest != 0; ++buf)
                    {
                        if (buf == 0)
                        {
                            throw new ScriptException(EResult.UnexpectedEndOfFile, location, "macro parameter list incomplete");
                        }
                        arg.Append(buf);
                        nest += buf == '(' ? 1 : 0;
                        nest -= buf == ')' ? 1 : 0;
                    }
                    args.Add(arg.ToString());
                }
            }
            ++buf;             // Skip over the ')'

            // Add a blank argument to distinguish between "TAG()" and "TAG"
            if (args.Count == 0)
            {
                args.Add(string.Empty);
            }

            // Check enough parameters have been given
            if (!identifiers && m_params.Count != args.Count)
            {
                throw new ScriptException(EResult.ParameterCountMismatch, location, "incorrect number of macro parameters");
            }

            return(true);
        }