/* Try to interpret LINE (a null-terminated string) as a variable definition.
         * If LINE was recognized as a variable definition, a pointer to its `struct
         * variable' is returned.  If LINE is not a variable definition, NULL is returned.  */

        public static variable assign_variable_definition(ref variable v, string line)
        {
            string          beg    = "";
            variable_flavor flavor = new variable_flavor();
            string          name   = "";

            beg  = Utils.next_token(line);
            line = parse_variable_definition(beg, ref flavor);
            if (line == null)
            {
                return(null);
            }

            int endindex = 0;

            if (flavor == variable_flavor.f_append)
            {
                endindex = beg.IndexOf("+=");
            }
            else
            {
                if (flavor == variable_flavor.f_recursive)
                {
                    endindex = beg.IndexOf("=");
                }
                else
                {
                    endindex = beg.IndexOf(":=");
                    if (endindex < 0)
                    {
                        endindex = beg.IndexOf("?=");
                    }
                }
            }
            name = beg.Substring(0, endindex).Trim();


            line     = line.Trim();
            v.value  = line;
            v.flavor = flavor;

            // Expand the name, so "$(foo)bar = baz" works.
            v.name = ExpandString(name);

            if (v.name.Length == 0)
            {
                MessageBox.Show("Empty variable name - (" + (new StackFrame()).GetFileName() + " - " + (new StackFrame()).GetFileLineNumber() + ")");
            }
            return(v);
        }
        // Given a variable, a value, and a flavor, define the variable. See the try_variable_definition() function for details on the parameters.
        public static variable do_variable_definition(string varname, string value, variable_origin origin, variable_flavor flavor, int target_var)
        {
            string   p           = "";
            string   alloc_value = "";
            variable v;
            bool     append      = false;
            bool     conditional = false;

            // Calculate the variable's new value in VALUE.
            switch (flavor)
            {
            default:
            case variable_flavor.f_bogus:
                //Should not be possible.
                MessageBox.Show("Abort!");
                break;

            case variable_flavor.f_simple:
                /* A simple variable definition "var := value".  Expand the value. We have to allocate memory since otherwise it'll clobber the
                 * variable buffer, and we may still need that if we're looking at a target-specific variable.  */
                p = alloc_value = ExpandString(value);
                break;

            case variable_flavor.f_conditional:
                // A conditional variable definition "var ?= value". The value is set IFF the variable is not defined yet.
                v = lookup_variable(varname);
                if (v != null)
                {
                    return(v.special? set_special_var(v) : v);
                }
                conditional = true;
                flavor      = variable_flavor.f_recursive;
                p           = value;
                break;

            case variable_flavor.f_recursive:
                // A recursive variable definition "var = value". The value is used verbatim.
                p = value;
                break;

            case variable_flavor.f_append:
            {
                // If we have += but we're in a target variable context, we want to append only with other variables in the context of this target.
                if (target_var == 1)
                {
                    append = true;
                    v      = lookup_variable_in_set(varname, varname.Length, current_variable_set_list);

                    // Don't append from the global set if a previous non-appending target-specific variable definition exists.
                    if (v != null && !v.append)
                    {
                        append = false;
                    }
                }
                else
                {
                    v = lookup_variable(varname);
                }

                if (v == null)
                {
                    // There was no old value. This becomes a normal recursive definition.
                    p      = value;
                    flavor = variable_flavor.f_recursive;
                }
                else
                {
                    // Paste the old and new values together in VALUE.
                    string val = "";
                    string tp  = "";
                    val = value;
                    if (v.recursive)
                    {
                        // The previous definition of the variable was recursive. The new value is the unexpanded old and new values.
                        flavor = variable_flavor.f_recursive;
                    }
                    else
                    {
                        /* The previous definition of the variable was simple. The new value comes from the old value, which was expanded
                         * when it was set; and from the expanded new value.  Allocate memory for the expansion as we may still need the rest of the
                         * buffer if we're looking at a target-specific variable.  */
                        val = tp = ExpandString(val);
                    }

                    p = alloc_value = v.value + " " + val;
                }
                break;
            }
            }

            /* If we are defining variables inside an $(eval ...), we might have a
             * different variable context pushed, not the global context (maybe we're inside a $(call ...) or something.  Since this function is only ever
             * invoked in places where we want to define globally visible variables, make sure we define this variable in the global set.  */

            v             = define_variable_in_set(varname, p, origin, (flavor == variable_flavor.f_recursive), (target_var == 1 ? current_variable_set_list : null));
            v.append      = append;
            v.conditional = conditional;
            return(v.special ? set_special_var(v) : v);
        }
        /* Parse P (a null-terminated string) as a variable definition. If it is not a variable definition, return NULL.
         * If it is a variable definition, return a pointer to the char after the assignment token and set *FLAVOR to the type of variable assignment.  */
        public static string parse_variable_definition(string p, ref variable_flavor flavor)
        {
            bool wspace = false;
            int  i;

            for (i = 0; i < p.Length; i++)
            {
                if (p[i] == '$')
                {
                    char closeparen;
                    char openparen = p[i + 1];
                    if (openparen == '(')
                    {
                        closeparen = ')';
                    }
                    else if (openparen == '{')
                    {
                        closeparen = '}';
                    }
                    else
                    {
                        // '$$' or '$X'.  Either way, nothing special to do here.
                        continue;
                    }

                    int count = 0;
                    for (; i < p.Length; i++)
                    {
                        if (p[i] == openparen)
                        {
                            ++count;
                        }
                        else if (p[i] == closeparen)
                        {
                            if (--count < 0)
                            {
                                i++;
                                break;
                            }
                        }
                    }
                    continue;
                }

                if (Utils.isblank(p[i]))
                {
                    wspace = true;
                    continue;
                }

                if (p[i] == '=')
                {
                    flavor = variable_flavor.f_recursive;
                    return(p.Substring(i + 1));
                }

                // Match assignment variants (:=, +=, ?=)
                if ((i + 1) < p.Length && p[i + 1] == '=')
                {
                    switch (p[i])
                    {
                    case ':':
                        flavor = variable_flavor.f_simple;
                        break;

                    case '+':
                        flavor = variable_flavor.f_append;
                        break;

                    case '?':
                        flavor = variable_flavor.f_conditional;
                        break;

                    default:
                        // If we skipped whitespace, non-assignments means no var.
                        if (wspace)
                        {
                            return(null);
                        }

                        // Might be assignment, or might be $= or #=.  Check.
                        continue;
                    }
                    return(p.Substring(i + 2));
                }
                else if (p[i] == ':')
                {
                    return(null);
                }
                // If we skipped whitespace, non-assignments means no var.
                if (wspace)
                {
                    return(null);
                }
            }
            if (i >= p.Length)
            {
                return(null);
            }
            return(p.Substring(i));
        }