예제 #1
0
        internal IOperand CompileForm([NotNull] IRoutineBuilder rb, [NotNull] ZilForm form, bool wantResult,
                                      IVariable resultStorage)
        {
            using (DiagnosticContext.Push(form.SourceLine))
            {
                var unwrapped = form.Unwrap(Context);

                if (!ReferenceEquals(unwrapped, form))
                {
                    switch (unwrapped)
                    {
                    case ZilForm newForm:
                        form = newForm;
                        break;

                    default:
                        return(wantResult ? CompileAsOperand(rb, unwrapped, form.SourceLine, resultStorage) : null);
                    }
                }

                if (!(form.First is ZilAtom head))
                {
                    Context.HandleError(new CompilerError(form, CompilerMessages.FORM_Must_Start_With_An_Atom));
                    return(wantResult ? Game.Zero : null);
                }

                // built-in statements handled by ZBuiltins
                var zversion = Context.ZEnvironment.ZVersion;
                Debug.Assert(form.Rest != null);
                var argCount = form.Rest.Count();

                if (wantResult)
                {
                    // prefer the value version, then value+predicate, predicate, void
                    if (ZBuiltins.IsBuiltinValueCall(head.Text, zversion, argCount))
                    {
                        return(ZBuiltins.CompileValueCall(head.Text, this, rb, form, resultStorage));
                    }
                    if (ZBuiltins.IsBuiltinValuePredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        resultStorage = resultStorage ?? rb.Stack;
                        ZBuiltins.CompileValuePredCall(head.Text, this, rb, form, resultStorage, label1, true);
                        rb.MarkLabel(label1);
                        return(resultStorage);
                    }
                    if (ZBuiltins.IsBuiltinPredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        var label2 = rb.DefineLabel();
                        resultStorage = resultStorage ?? rb.Stack;
                        ZBuiltins.CompilePredCall(head.Text, this, rb, form, label1, true);
                        rb.EmitStore(resultStorage, Game.Zero);
                        rb.Branch(label2);
                        rb.MarkLabel(label1);
                        rb.EmitStore(resultStorage, Game.One);
                        rb.MarkLabel(label2);
                        return(resultStorage);
                    }
                    if (ZBuiltins.IsBuiltinVoidCall(head.Text, zversion, argCount))
                    {
                        ZBuiltins.CompileVoidCall(head.Text, this, rb, form);
                        return(Game.One);
                    }
                }
                else
                {
                    // prefer the void version, then predicate, value, value+predicate
                    // (predicate saves a cleanup instruction)
                    if (ZBuiltins.IsBuiltinVoidCall(head.Text, zversion, argCount))
                    {
                        ZBuiltins.CompileVoidCall(head.Text, this, rb, form);
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinPredCall(head.Text, zversion, argCount))
                    {
                        var dummy = rb.DefineLabel();
                        ZBuiltins.CompilePredCall(head.Text, this, rb, form, dummy, true);
                        rb.MarkLabel(dummy);
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinValueCall(head.Text, zversion, argCount))
                    {
                        if (ZBuiltins.CompileValueCall(head.Text, this, rb, form, null) == rb.Stack)
                        {
                            rb.EmitPopStack();
                        }
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinValuePredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        ZBuiltins.CompileValuePredCall(head.Text, this, rb, form, rb.Stack, label1, true);
                        rb.MarkLabel(label1);
                        rb.EmitPopStack();
                        return(null);
                    }
                }

                // routine calls
                var obj = Context.GetZVal(Context.ZEnvironment.InternGlobalName(head));

                while (obj is ZilConstant cnst)
                {
                    obj = cnst.Value;
                }

                switch (obj)
                {
                case ZilRoutine rtn:
                    // check argument count
                    var args = form.Skip(1).ToArray();
                    if (args.Length < rtn.ArgSpec.MinArgCount ||
                        rtn.ArgSpec.MaxArgCount != null && args.Length > rtn.ArgSpec.MaxArgCount)
                    {
                        Context.HandleError(CompilerError.WrongArgCount(
                                                rtn.Name?.ToString() ?? "<unnamed routine>",
                                                new ArgCountRange(rtn.ArgSpec.MinArgCount, rtn.ArgSpec.MaxArgCount)));
                        return(wantResult ? Game.Zero : null);
                    }

                    // compile routine call
                    resultStorage = wantResult ? (resultStorage ?? rb.Stack) : null;
                    using (var argOperands = CompileOperands(rb, form.SourceLine, args))
                    {
                        rb.EmitCall(Routines[head], argOperands.AsArray(), resultStorage);
                    }
                    return(resultStorage);

                case ZilFalse _:
                    // this always returns 0. we can eliminate the call if none of the arguments have side effects.
                    var argsWithSideEffects = form.Skip(1).Where(HasSideEffects).ToArray();

                    if (argsWithSideEffects.Length <= 0)
                    {
                        return(Game.Zero);
                    }

                    resultStorage = wantResult ? (resultStorage ?? rb.Stack) : null;
                    using (var argOperands = CompileOperands(rb, form.SourceLine, argsWithSideEffects))
                    {
                        var operands = argOperands.AsArray();
                        if (operands.Any(o => o == rb.Stack))
                        {
                            rb.EmitCall(Game.Zero, operands.Where(o => o == rb.Stack).ToArray(), resultStorage);
                        }
                    }
                    return(resultStorage);

                default:
                    // unrecognized
                    if (!ZBuiltins.IsNearMatchBuiltin(head.Text, zversion, argCount, out var error))
                    {
                        error = new CompilerError(CompilerMessages.Unrecognized_0_1, "routine or instruction", head);
                    }
                    Context.HandleError(error);
                    return(wantResult ? Game.Zero : null);
                }
            }
        }