public Tuple<AST.Env, List<Tuple<AST.Env, AST.Decln>>> GetDeclns(AST.Env env) {

            // Get storage class, and base type.
            Tuple<AST.Env, AST.Decln.SCS, AST.ExprType> r_specs = decln_specs.GetSCSType(env);
            env = r_specs.Item1;
            AST.Decln.SCS scs = r_specs.Item2;
            AST.ExprType base_type = r_specs.Item3;

            List<Tuple<AST.Env, AST.Decln>> declns = new List<Tuple<AST.Env, AST.Decln>>();

            // For each init declarators, we'll generate a declaration.
            foreach (InitDeclr init_declr in init_declrs) {

                // Get the final type, name, and initializer.
                Tuple<AST.Env, AST.ExprType, Option<AST.Initr>, String> r_declr = init_declr.GetInitDeclr(env, base_type);
                env = r_declr.Item1;
                AST.ExprType type = r_declr.Item2;
                Option<AST.Initr> initr = r_declr.Item3;
                String name = r_declr.Item4;

                // Insert the new symbol into the environment.
                AST.Env.EntryKind kind;
                switch (scs) {
                    case AST.Decln.SCS.AUTO:
                        if (env.IsGlobal()) {
                            kind = AST.Env.EntryKind.GLOBAL;
                        } else {
                            kind = AST.Env.EntryKind.STACK;
                        }
                        break;
                    case AST.Decln.SCS.EXTERN:
                        kind = AST.Env.EntryKind.GLOBAL;
                        break;
                    case AST.Decln.SCS.STATIC:
                        kind = AST.Env.EntryKind.GLOBAL;
                        break;
                    case AST.Decln.SCS.TYPEDEF:
                        kind = AST.Env.EntryKind.TYPEDEF;
                        break;
                    default:
                        throw new InvalidOperationException();
                }
                env = env.PushEntry(kind, name, type);

                // Generate the declaration.
                declns.Add(Tuple.Create(env, new AST.Decln(name, scs, type, initr)));

            }

            return new Tuple<AST.Env, List<Tuple<AST.Env, AST.Decln>>>(env, declns);
        }
        public Tuple<AST.Env, AST.FuncDef> GetFuncDef(AST.Env env)
        {
            // Get storage class specifier and base type from declaration specifiers.
            Tuple<AST.Env, AST.Decln.SCS, AST.ExprType> r_specs = this.specs.GetSCSType(env);
            env = r_specs.Item1;
            AST.Decln.SCS scs = r_specs.Item2;
            AST.ExprType base_type = r_specs.Item3;

            // Get function name and function type from declarator.
            Tuple<String, AST.ExprType> r_declr = this.declr.GetNameAndType(env, base_type);
            String name = r_declr.Item1;
            AST.ExprType type = r_declr.Item2;

            AST.TFunction func_type;
            if (type.kind == AST.ExprType.Kind.FUNCTION) {
                func_type = (AST.TFunction)type;
            } else {
                throw new InvalidOperationException($"{name} is not a function.");
            }

            switch (scs) {
                case AST.Decln.SCS.AUTO:
                case AST.Decln.SCS.EXTERN:
                case AST.Decln.SCS.STATIC:
                    env = env.PushEntry(AST.Env.EntryKind.GLOBAL, name, type);
                    break;
                case AST.Decln.SCS.TYPEDEF:
                default:
                    throw new InvalidOperationException("Invalid storage class specifier for function definition.");
            }

            env = env.SetCurrentFunction(func_type);

            Tuple<AST.Env, AST.Stmt> r_stmt = this.stmt.GetStmt(env);
            env = r_stmt.Item1;
            AST.Stmt stmt = r_stmt.Item2;

            env = env.SetCurrentFunction(new AST.TEmptyFunction());

            return Tuple.Create(env, new AST.FuncDef(name, scs, func_type, stmt));
        }
        public Tuple<AST.Env, AST.ExprType> GetExprTypeEnv(Boolean is_struct, AST.Env env, Boolean is_const, Boolean is_volatile) {

            if (name == "") {
                // If no name is supplied: must be complete.
                // struct { ... } or union { ... }

                if (declns == null) {
                    throw new ArgumentNullException("Error: parser should ensure declns != null");
                }

                Tuple<AST.Env, List<Tuple<String, AST.ExprType>>> r_attribs = GetAttribs(env);
                env = r_attribs.Item1;

                if (is_struct) {
                    return new Tuple<AST.Env, AST.ExprType>(env, AST.TStructOrUnion.CreateStruct("<anonymous>", r_attribs.Item2, is_const, is_volatile));
                } else {
                    return new Tuple<AST.Env, AST.ExprType>(env, AST.TStructOrUnion.CreateUnion("<anonymous>", r_attribs.Item2, is_const, is_volatile));
                }

            } else {
                // If a name is supplied, split into 2 cases.

                String typename = is_struct ? $"struct {name}" : $"union {name}";

                if (declns == null) {
                    // Case 1: If no attribute list supplied, then we are either
                    //       1) mentioning an already-existed struct/union
                    //    or 2) creating an incomplete struct/union

                    Option<AST.Env.Entry> entry_opt = env.Find(typename);

                    if (entry_opt.IsNone) {
                        // If the struct/union is not in the current environment,
                        // then add an incomplete struct/union into the environment
                        AST.ExprType type =
                            is_struct
                            ? AST.TStructOrUnion.CreateIncompleteStruct(name, is_const, is_volatile)
                            : AST.TStructOrUnion.CreateIncompleteUnion(name, is_const, is_volatile);

                        env = env.PushEntry(AST.Env.EntryKind.TYPEDEF, typename, type);
                        return Tuple.Create(env, type);
                    }

                    if (entry_opt.Value.kind != AST.Env.EntryKind.TYPEDEF) {
                        throw new InvalidProgramException(typename + " is not a type? This should be my fault.");
                    }

                    // If the struct/union is found, return it.
                    return Tuple.Create(env, entry_opt.Value.type);

                } else {
                    // Case 2: If an attribute list is supplied.

                    // 1) Make sure there is no complete struct/union in the current environment.
                    Option<AST.Env.Entry> entry_opt = env.Find(typename);
                    if (entry_opt.IsSome && entry_opt.Value.type.kind == AST.ExprType.Kind.STRUCT_OR_UNION && ((AST.TStructOrUnion)entry_opt.Value.type).IsComplete) {
                        throw new InvalidOperationException($"Redefining {typename}");
                    }

                    // 2) Add an incomplete struct/union into the environment.
                    AST.TStructOrUnion type =
                        is_struct
                        ? AST.TStructOrUnion.CreateIncompleteStruct(name, is_const, is_volatile)
                        : AST.TStructOrUnion.CreateIncompleteUnion(name, is_const, is_volatile);
                    env = env.PushEntry(AST.Env.EntryKind.TYPEDEF, typename, type);

                    // 3) Iterate over the attributes.
                    Tuple<AST.Env, List<Tuple<String, AST.ExprType>>> r_attribs = GetAttribs(env);
                    env = r_attribs.Item1;

                    // 4) Make the type complete. This would also change the entry inside env.
                    if (is_struct) {
                        type.DefineStruct(r_attribs.Item2);
                    } else {
                        type.DefineUnion(r_attribs.Item2);
                    }

                    return new Tuple<AST.Env, AST.ExprType>(env, type);
                }
            }
        }
        public override Tuple<AST.Env, AST.ExprType> GetExprTypeEnv(AST.Env env, Boolean is_const, Boolean is_volatile) {
            if (enums == null) {
                // if there is no content in this enum type, we must find it's definition in the environment
                Option<AST.Env.Entry> entry_opt = env.Find($"enum {name}");
                if (entry_opt.IsNone || entry_opt.Value.kind != AST.Env.EntryKind.TYPEDEF) {
                    throw new InvalidOperationException($"Type 'enum {name}' has not been defined.");
                }
            } else {
                // so there are something in this enum type, we need to put this type into the environment
                Int32 idx = 0;
                foreach (Enumerator elem in enums) {
                    Tuple<AST.Env, String, Int32> r_enum = elem.GetEnumerator(env, idx);
                    env = r_enum.Item1;
                    String name = r_enum.Item2;
                    idx = r_enum.Item3;
                    env = env.PushEnum(name, new AST.TLong(), idx);
                    idx++;
                }
                env = env.PushEntry(AST.Env.EntryKind.TYPEDEF, "enum " + name, new AST.TLong());
            }

            return new Tuple<AST.Env, AST.ExprType>(env, new AST.TLong(is_const, is_volatile));
        }
        public override AST.Expr GetExpr(AST.Env env)
        {
            // Step 1: get arguments passed into the function.
            // Note that currently the arguments are not casted based on the prototype.
            var args = this.args.Select(_ => _.GetExpr(env)).ToList();

            // A special case:
            // If we cannot find the function prototype in the environment, make one up.
            // This function returns int.
            // Update the environment to add this function type.
            if ((this.func is Variable) && env.Find((this.func as Variable).name).IsNone) {
                // TODO: get this env used.
                env = env.PushEntry(
                    loc: AST.Env.EntryKind.TYPEDEF,
                    name: (this.func as Variable).name,
                    type: AST.TFunction.Create(
                        ret_type: new AST.TLong(is_const: true),
                        args: args.ConvertAll(_ => Tuple.Create("", _.type)),
                        is_varargs: false
                    )
                );
            }

            // Step 2: get function expression.
            AST.Expr func = this.func.GetExpr(env);

            // Step 3: get the function type.
            AST.TFunction func_type;
            switch (func.type.kind) {
                case AST.ExprType.Kind.FUNCTION:
                    func_type = func.type as AST.TFunction;
                    break;

                case AST.ExprType.Kind.POINTER:
                    var ref_t = (func.type as AST.TPointer).ref_t;
                    if (!(ref_t is AST.TFunction)) {
                        throw new InvalidOperationException("Expected a function pointer.");
                    }
                    func_type = ref_t as AST.TFunction;
                    break;

                default:
                    throw new InvalidOperationException("Expected a function in function call.");
            }

            Int32 num_args_prototype = func_type.args.Count;
            Int32 num_args_actual = args.Count;

            // If this function doesn't take varargs, make sure the number of arguments match that in the prototype.
            if (!func_type.is_varargs && num_args_actual != num_args_prototype) {
                throw new InvalidOperationException("Number of arguments mismatch.");
            }

            // Anyway, you can't call a function with fewer arguments than the prototype.
            if (num_args_actual < num_args_prototype) {
                throw new InvalidOperationException("Too few arguments.");
            }

            // Make implicit cast.
            args = Enumerable.Zip(
                args.GetRange(0, num_args_prototype),
                func_type.args,
                (arg, entry) => AST.TypeCast.MakeCast(arg, entry.type)
            ).Concat(args.GetRange(num_args_prototype, num_args_actual - num_args_prototype)).ToList();

            return new AST.FuncCall(func, func_type, args);
        }