/// <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)); } }
/// <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(); }
/// <summary> /// Expands the macro into the string 'exp' with the text of this macro including substituted parameter text.</summary> private string Expand(List <string> args, Loc location) { if (args.Count != m_params.Count) { throw new ScriptException(EResult.ParameterCountMismatch, location, "macro parameter count mismatch"); } // Set the string to the macro text initially var exp = new StringBuilder(m_expansion); // Substitute each parameter for (int i = 0; i != m_params.Count; ++i) { var what = m_params[i]; if (what.Length == 0) { continue; } var len = what.Length; // Replace the instances of 'what' with 'with' string with; for (int j = Str_.IndexOfIdentifier(exp.ToString(), what, 0); j != exp.Length; j = Str_.IndexOfIdentifier(exp.ToString(), what, j += len)) { // If the identifier is prefixed with '##' then just remove the '##' // this will have the effect of concatenating the substituted strings. if (j >= 2 && exp[j - 1] == '#' && exp[j - 2] == '#') { j -= 2; len += 2; with = args[i]; } // If the identifier is prefixed with '#' then replace 'what' with 'with' as a literal string else if (j >= 1 && exp[j - 1] == '#') { j -= 1; len += 1; with = args[i]; with.Replace("\"", "\\\""); with = with.Quotes(add: true); } // Otherwise, normal substitution else { with = args[i]; } // Do the substitution exp.Remove(j, len); exp.Insert(j, with); j += with.Length - len; } } return(exp.ToString()); }
/// <summary>Decode byte data from a registry key</summary> public static object?Decode(RegistryValueKind kind, byte[] data) { switch (kind) { case RegistryValueKind.None: { return(null); } case RegistryValueKind.Unknown: { return(data); } case RegistryValueKind.String: { return(Encoding.Unicode.GetString(data, 0, data.Length).TrimEnd('\0')); } case RegistryValueKind.ExpandString: { var re = new Regex("%(.*?)%", RegexOptions.None); var str = Encoding.Unicode.GetString(data, 0, data.Length).TrimEnd('\0'); return(re.Replace(str, m => Environment.GetEnvironmentVariable(m.Groups[1].Value))); } case RegistryValueKind.Binary: { return(data); } case RegistryValueKind.DWord: { return(BitConverter.ToUInt32(data, 0)); } case RegistryValueKind.MultiString: { var multi_str = Encoding.Unicode.GetString(data, 0, data.Length); var strings = Str_.DecodeStringArray(multi_str); return(strings); } case RegistryValueKind.QWord: { return(BitConverter.ToUInt64(data, 0)); } default: { throw new Exception($"Registry property kind {kind} unsupported"); } } }
/// <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); }
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); }
/// <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); }