module otya.smilebasic.builtinfunctions;

import std.conv;
import std.typecons;
import std.typetuple;
import std.traits;
import std.stdio;
import std.ascii;
import std.range;
import std.string;
import std.algorithm;
import otya.smilebasic.error;
import otya.smilebasic.type;
import otya.smilebasic.petitcomputer;
import otya.smilebasic.sprite;
import otya.smilebasic.vm;
//プチコンの引数省略は特殊なので
//LOCATE ,,0のように省略できる
struct DefaultValue(T, bool skippable = true)
{
    T value;
    bool isDefault;
    this(T v, bool f)
    {
        value = v;
        isDefault = f;
    }
    this(T v)
    {
        value = v;
        isDefault = false;
    }
    this(bool f)
    {
        isDefault = f;
    }
    void setDefaultValue(T v)
    {
        if(isDefault)
            value = v;
    }
    mixin Proxy!value;
}
struct StartOptional
{
    const char[] name;
}
struct BasicName
{
    wstring naame;
}
alias ValueType = otya.smilebasic.type.ValueType;
struct BuiltinFunctionArgument
{
    ValueType argType;
    bool optionalArg;
    bool skipArg;
}
//オーバーロード用
class BuiltinFunctions
{
    private BuiltinFunction[] func;
    this(BuiltinFunction f)
    {
        func = new BuiltinFunction[1];
        func[0] = f;
    }
    void addFunction(BuiltinFunction func)
    {
        this.func ~= func;
    }
    BuiltinFunction overloadResolution(size_t argc, size_t outargc)
    {
        BuiltinFunction va;
        //とりあえず引数の数で解決させる,というよりコンパイル時に型を取得する方法がない
        foreach(f; func)
        {
            if(f.startskip <= argc && f.argments.length >= argc)
                if((f.outoptional != 0 && f.outoptional <= outargc && f.results.length >= outargc) || f.results.length == outargc)
                return f;
            if(f.variadic)
                va = f;
        }
        //一応可変長は最後
        if(va && ((va.outoptional != 0 && va.outoptional <= outargc && va.results.length >= outargc) || va.results.length == outargc)) return va;
        writeln("====function overloads===");
        writefln("func = %s argc = %d outargc = %d", func[0].name, argc, outargc);
        foreach(f; func)
        {
            writefln("name=\"%s\", argments=%s, results = %s, variadic = %s, startoptional = %d, function pointer=%s", f.name, f.argments, f.results, f.variadic, f.startskip, f.func);
        }
        //引数数ちがうのは実行前にエラー
        throw new IllegalFunctionCall(func[0].name);
    }
}
alias DefaultValue!(int, false) optionalint;
alias DefaultValue!(int, false) optionaldouble;
alias DefaultValue!(int, false) optionalstring;
template smilebasicFunctionName(string func = __FUNCTION__)
{
    static fn = func[func.lastIndexOf('.') + 1..$];
    auto smilebasicFunctionName()
    {
        return fn;
    }
}
/**
ここに関数を定義すればコンパイル時にBuiltinFunctionに変換してくれる便利なクラス
*/
class BuiltinFunction
{
    BuiltinFunctionArgument[] argments;
    BuiltinFunctionArgument[] results;
    void function(PetitComputer, Value[], Value[]) func;
    int startskip;
    int outoptional;
    bool variadic;
    string name;
    this(BuiltinFunctionArgument[] argments, BuiltinFunctionArgument[] results, void function(PetitComputer, Value[], Value[]) func, int startskip,
         bool variadic, string name, int outoptional)
    {
        this.argments = argments;
        this.results = results;
        this.func = func;
        this.startskip = startskip;
        this.variadic = variadic;
        this.name = name;
        this.outoptional = outoptional;
    }
    bool hasSkipArgument()
    {
        return this.startskip != this.argments.length;
    }
    import std.math;
    /*
    static pure double ABS(double a)
    {
        return a < 0 ? -a : a;
    }*/
    //static double function(double) ABS = &abs!double;
    //static double function(double) SGN = &sgn!double;
    static Value ABS(Value arg1)
    {
        if (arg1.isInteger)
        {
            return Value(abs(arg1.integerValue));
        }
        else
        {
            return Value(abs(arg1.castDouble));
        }
    }
    static pure nothrow @nogc @trusted int SGN(double arg1)
    {
        if (isNaN(arg1))
        {
            return 1;
        }
        return arg1 > 0 ? 1 : arg1 ? -1 : 0;
    }
    static pure nothrow double SIN(double arg1)
    {
        return sin(arg1);
    }
    static double ASIN(double arg1)
    {
        if (arg1 >= -1 && arg1 <= 1)
        {
            return asin(arg1);
        }
        else
        {
            throw new OutOfRange();
        }
    }
    static pure nothrow double SINH(double arg1)
    {
        return sinh(arg1);
    }
    static pure nothrow double COS(double arg1)
    {
        return cos(arg1);
    }
    static double ACOS(double arg1)
    {
        if (arg1 >= -1 && arg1 <= 1)
        {
            return acos(arg1);
        }
        else
        {
            throw new OutOfRange();
        }
    }
    static pure nothrow double COSH(double arg1)
    {
        return cosh(arg1);
    }
    static pure nothrow double TAN(double arg1)
    {
        return tan(arg1);
    }
    static pure nothrow double ATAN(double arg1, DefaultValue!(double, false) arg2)
    {
        if(arg2.isDefault)
        {
            return atan(arg1);
        }
        return atan2(arg1, cast(double)arg2);
    }
    static pure nothrow double TANH(double arg1)
    {
        return tanh(arg1);
    }
    enum Classify
    {
        NORMAL = 0,
        INFINITY = 1,
        NAN = 2,
    }
    static pure nothrow int CLASSIFY(double arg)
    {
        if (arg.isNaN)
        {
            return Classify.NAN;
        }
        if (arg.isInfinity)
        {
            return Classify.INFINITY;
        }
        return Classify.NORMAL;
    }
    static pure nothrow double RAD(double arg1)
    {
        return arg1 * std.math.PI / 180;
    }
    static pure nothrow double DEG(double arg1)
    {
        return arg1 * 180 / std.math.PI;
    }
    static pure nothrow double PI()
    {
        return std.math.PI;
    }
    //static ABS = function double(double x) => abs(this.result == ValueType.Double ? 1 : 0);
    static void LOCATE(PetitComputer p, DefaultValue!int x, DefaultValue!int y, int z)
    {
        if (!x.isDefault && x < 0 || x >= p.console.consoleWidthC)
            throw new OutOfRange(smilebasicFunctionName, 1);
        if (!y.isDefault && y < 0 || y >= p.console.consoleHeightC)
            throw new OutOfRange(smilebasicFunctionName, 2);
        x.setDefaultValue(p.console.CSRX);
        y.setDefaultValue(p.console.CSRY);
        if (z < -256 || z > 1024)
            throw new OutOfRange(smilebasicFunctionName, 3);
        p.console.CSRX = cast(int)x;
        p.console.CSRY = cast(int)y;
        p.console.CSRZ = cast(int)z;
    }
    static void LOCATE(PetitComputer p, DefaultValue!int x, DefaultValue!int y)
    {
        if (!x.isDefault && x < 0 || x >= p.console.consoleWidthC)
            throw new OutOfRange(smilebasicFunctionName, 1);
        if (!y.isDefault && y < 0 || y >= p.console.consoleHeightC)
            throw new OutOfRange(smilebasicFunctionName, 2);
        x.setDefaultValue(p.console.CSRX);
        y.setDefaultValue(p.console.CSRY);
        p.console.CSRX = cast(int)x;
        p.console.CSRY = cast(int)y;
    }
    static void COLOR(PetitComputer p, DefaultValue!int fore, DefaultValue!(int, false) back)
    {
        fore.setDefaultValue(p.console.foreColor);
        back.setDefaultValue(p.console.backColor);
        p.console.foreColor = cast(int)fore;
        p.console.backColor = cast(int)back;
    }
    static void ATTR(PetitComputer p, int attr)
    {
        if (attr < 0 || attr > 15)
            throw new OutOfRange("ATTR", 1);
        p.console.attr = cast(otya.smilebasic.console.ConsoleAttribute)attr;
    }
    static void WIDTH(PetitComputer p, int width)
    {
        if (width != 8 && width != 16)
        {
            throw new IllegalFunctionCall("WIDTH", 1);
        }
        p.console.width = width;
    }
    static int WIDTH(PetitComputer p)
    {
        return p.console.width;
    }
    static void VSYNC(PetitComputer p)
    {
        VSYNC(p, 1);
    }
    static void VSYNC(PetitComputer p, int time)
    {
        p.vsync(time);
    }
    static void WAIT(PetitComputer p, DefaultValue!int time)
    {
        time.setDefaultValue(1);
        p.wait(cast(int)time);
    }
    //TODO:プチコンのCLSには引数の個数制限がない
    static void CLS(PetitComputer p/*vaarg*/)
    {
        p.console.cls;
    }
    static void ASSERT__(PetitComputer p, int cond, wstring message)
    {
        if(!cond)
        {
            p.console.print("Assertion failed: ", message, "\n");
        }
        assert(cond, message.to!string);
    }
    static int BUTTON(PetitComputer p, DefaultValue!(int, false) mode, DefaultValue!(int, false) mp)
    {
        if(!mp.isDefault)
        {
            writeln("NOTIMPL:BUTTON(ID, MPID)");
        }
        return p.button;
    }
    static void BREPEAT(PetitComputer p, int btnid, int startTime, int interval)
    {
        stderr.writefln("NOTIMPL:BREPEAT %d, %d, %d", btnid, startTime, interval);
    }
    static void VISIBLE(PetitComputer p, int console, int graphic, int BG, int sprite)
    {
        import std.exception : enforce;
        enforce(console == 0 || console == 1, new OutOfRange("VISIBLE", 1));
        enforce(graphic == 0 || graphic == 1, new OutOfRange("VISIBLE", 2));
        enforce(BG == 0 || BG == 1, new OutOfRange("VISIBLE", 3));
        enforce(sprite == 0 || sprite == 1, new OutOfRange("VISIBLE", 4));
        p.console.visible = cast(bool)console;
        p.graphic.visible = cast(bool)graphic;
        p.BGvisible = cast(bool)BG;
        p.sprite.visible = cast(bool)sprite;
    }
    static void BACKCOLOR(PetitComputer p, int color)
    {
        p.backcolor = color;
    }
    static int BACKCOLOR(PetitComputer p)
    {
        return p.backcolor;
    }
    static void TOUCH(PetitComputer p, out int tm, out int tchx, out int tchy)
    {
        auto pos = p.touchPosition;
        tm = pos.tm;
        if (p.x.mode == PetitComputer.XMode.WIIU)
        {
            tchx = pos.display1X;
            tchy = pos.display1Y;
        }
        else
        {
            tchx = pos.x;
            tchy = pos.y;
        }
    }
    static void TOUCH(PetitComputer p, int id, out int tm, out int tchx, out int tchy)
    {
        //BIG
        if (p.x.mode == PetitComputer.XMode.WIIU)
        {
            auto pos = p.touchPosition;
            tm = pos.tm;
            if (id == 0)
            {
                tchx = pos.display1X;
                tchy = pos.display1Y;
            }
            else
            {
                tchx = pos.gamepadX;
                tchy = pos.gamepadY;
            }
        }
        else
        {
            writeln("NOTIMPL:TOUCH MPID");
            tm = tchx = tchy = 0;
        }
    }
    static void XSCREEN(PetitComputer p, int mode, int tv, int gamepad, int sp, int bg)
    {
        if (p.x.mode != PetitComputer.XMode.WIIU || mode != 6)
        {
            throw new IllegalFunctionCall("XSCREEN");
        }
        p.xscreen(mode, tv, gamepad, sp, bg);
    }
    static void XSCREEN(PetitComputer p, int mode, int tv, int sp, int bg)
    {
        if (p.x.mode != PetitComputer.XMode.WIIU || mode != 5)
        {
            throw new IllegalFunctionCall("XSCREEN");
        }
        p.xscreen(mode, tv, sp, bg);
    }
    static void XSCREEN(PetitComputer p, int mode, int tv)
    {
        XSCREEN(p, mode, tv, 4096, 4);
    }
    static void XSCREEN(PetitComputer p, int mode)
    {
        if(mode == 2 || mode == 3)
        {
            p.xscreen(mode, 256, 2);
        }
        else
        {
            p.xscreen(mode, 512, 4);
        }
    }
    static void XSCREEN(PetitComputer p, int mode, int sp, int bg)
    {
        if (mode == 6)
        {
            XSCREEN(p, mode, sp, bg, 2048, 2);
            return;
        }
        p.xscreen(mode, sp, bg);
    }
    static void DISPLAY(PetitComputer p, int display)
    {
        if (p.currentDisplay.count <= display || display < 0)
            throw new OutOfRange("DISPLAY", 1);
        p.display(display);
    }
    static int DISPLAY(PetitComputer p)
    {
        return p.displaynum;
    }
    static void GCLS(PetitComputer p)
    {
        p.graphic.gcls(p.graphic.useGRP, 0);
    }
    static void GCLS(PetitComputer p, int color)
    {
        p.graphic.gcls(p.graphic.useGRP, color);
    }
    static void GPSET(PetitComputer p, int x, int y, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gpset(p.graphic.useGRP, x, y, cast(int)color);
    }
    static void GLINE(PetitComputer p, int x, int y, int x2, int y2, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gline(p.graphic.useGRP, x, y, x2, y2, cast(int)color);
    }
    static void GBOX(PetitComputer p, int x, int y, int x2, int y2, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gbox(p.graphic.useGRP, x, y, x2, y2, cast(int)color);
    }
    static void GFILL(PetitComputer p, int x, int y, int x2, int y2, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gfill(p.graphic.useGRP, x, y, x2, y2, cast(int)color);
    }
    //X,Y,R[,COLOR]
    //X,Y,R,SR,ER[,COLOR]
    static void GCIRCLE(PetitComputer p, int x, int y, int r, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gcircle(p.graphic.useGRP, x, y, r, cast(int)color);
    }
    static void GCIRCLE(PetitComputer p, int x, int y, int r, int sr, int er, DefaultValue!(int, false) flag, DefaultValue!(int, false) color)
    {
        color.setDefaultValue(p.graphic.gcolor);
        flag.setDefaultValue(0);
        p.graphic.gcircle(p.graphic.useGRP, x, y, r, sr, er, cast(int)flag, cast(int)color);
    }
    static void GCOLOR(PetitComputer p, int color)
    {
        p.graphic.gcolor = color;
    }
    static int GCOLOR(PetitComputer p)
    {
        return p.graphic.gcolor;
    }
    static void GPRIO(PetitComputer p, int z)
    {
        p.graphic.gprio = z;
    }
    static void GPAGE(PetitComputer p, int showPage, int usePage)
    {
        p.graphic.showGRP = showPage;
        p.graphic.useGRP = usePage;
    }
    static void GPAGE(PetitComputer p, out int showPage, out int usePage)
    {
        showPage = p.graphic.showGRP;
        usePage = p.graphic.useGRP;
    }
    static void GCLIP(PetitComputer p, int clipmode)
    {
        p.graphic.clip(cast(bool)clipmode/*not checked*/);
    }
    static void GCLIP(PetitComputer p, int clipmode, int sx, int sy, int ex, int ey)
    {
        if (sx < 0 || sy < 0 || ex < 0 || ey < 0)
            throw new OutOfRange("GCLIP");
        if (clipmode)
        {
            if (sx >= p.graphic.width || sy >= p.graphic.height || ex >= p.graphic.width || ey >= p.graphic.height)
                throw new OutOfRange("GCLIP");
        }
        if (sx > ex)
        {
            swap(sx, ex);
        }
        if (sy > ey)
        {
            swap(sy, ey);
        }
        int x = sx;
        int y = sy;
        int w = ex - sx + 1;
        int h = ey - sy + 1;
        p.graphic.clip(cast(bool)clipmode/*not checked*/, x, y, w, h);
    }
    static void GPAINT(PetitComputer p, int x, int y, DefaultValue!(int, false) color, DefaultValue!(int, false) color2)
    {
        color.setDefaultValue(p.graphic.gcolor);
        p.graphic.gpaint(p.graphic.useGRP, x, y, cast(int)color);
    }
    static void GPUTCHR(PetitComputer p, int x, int y, Value str)
    {
        GPUTCHR(p, x, y, str, 1, 1, p.graphic.gcolor);
    }
    static void GPUTCHR(PetitComputer p, int x, int y, Value str, int color)
    {
        GPUTCHR(p, x, y, str, 1, 1, color);
    }
    static void GPUTCHR(PetitComputer p, int x, int y, Value str, int scalex, int scaley)
    {
        GPUTCHR(p, x, y, str, scalex, scaley, p.graphic.gcolor);
    }
    static void GPUTCHR(PetitComputer p, int x, int y, Value str, int scalex, int scaley, int color)
    {
        if (str.isNumber)
        {
            p.graphic.gputchr(p.graphic.useGRP, x, y, str.castInteger, scalex, scaley, color);
        }
        else if (str.isString)
        {
            p.graphic.gputchr(p.graphic.useGRP, x, y, str.castDString, scalex, scaley, color);
        }
        else
        {
            throw new TypeMismatch("GPUTCHR", 3);
        }
    }
    static int GSPOIT(PetitComputer p, int x, int y)
    {
        return p.graphic.gspoit(p.graphic.useGRP, x, y);
    }
    static void GSAVE(PetitComputer p, int savepage, int x, int y, int w, int h, Value ary, int flag)
    {
        if (w < 0)
            throw new OutOfRange("GSAVE", 4);
        if (h < 0)
            throw new OutOfRange("GSAVE", 5);
        if (!ary.isNumberArray)
            throw new TypeMismatch("GSAVE", 6);
        int size = w * h;
        if (ary.type == ValueType.IntegerArray)
        {
            if (size > ary.length)
            {
                ary.integerArray.length = size;
            }
            p.graphic.gsave(savepage, x, y, w, h, ary.integerArray.array, flag);
        }
        else if (ary.type == ValueType.DoubleArray)
        {
            if (size > ary.length)
            {
                ary.doubleArray.length = size;
            }
            p.graphic.gsave(savepage, x, y, w, h, ary.doubleArray.array, flag);
        }   
    }
    static void GSAVE(PetitComputer p, int x, int y, int w, int h, Value ary, int flag)
    {
        if (w < 0)
            throw new OutOfRange("GSAVE", 3);
        if (h < 0)
            throw new OutOfRange("GSAVE", 4);
        if (!ary.isNumberArray)
            throw new TypeMismatch("GSAVE", 5);
        GSAVE(p, p.graphic.useGRP, x, y, w, h, ary, flag);
    }
    static void GSAVE(PetitComputer p, int savepage, Value ary, int flag)
    {
        if (!ary.isNumberArray)
            throw new TypeMismatch("GSAVE", 2);
        auto writeArea = p.graphic.writeArea[p.displaynum];
        GSAVE(p, savepage, writeArea.x, writeArea.y, writeArea.w, writeArea.h, ary, flag);
    }
    static void GSAVE(PetitComputer p, Value ary, int flag)
    {
        if (!ary.isNumberArray)
            throw new TypeMismatch("GSAVE", 1);
        GSAVE(p, p.graphic.useGRP, ary, flag);
    }
    static void GLOAD(PetitComputer p, int x, int y, int w, int h, Value ary, Value flagOrPalette, int copymode)
    {
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("GLOAD", 5);
        }
        if (w < 0)
            throw new OutOfRange("GLOAD", 3);
        if (h < 0)
            throw new OutOfRange("GLOAD", 4);
        if (w * h > ary.length)
        {
            throw new SubscriptOutOfRange("GLOAD", 5);
        }
        if (flagOrPalette.isNumber)
        {
            int colorflag = flagOrPalette.castInteger;
            if (ary.type == ValueType.IntegerArray)
            {
                p.graphic.gload(x, y, w, h, ary.integerArray.array, colorflag, copymode);
            }
            if (ary.type == ValueType.DoubleArray)
            {
                p.graphic.gload(x, y, w, h, ary.doubleArray.array, colorflag, copymode);
            }
        }
        else if (flagOrPalette.isNumberArray)
        {
            if (ary.type == ValueType.IntegerArray)
            {
                if (flagOrPalette.type == ValueType.IntegerArray)
                {
                    p.graphic.gloadPalette(x, y, w, h, ary.integerArray.array, flagOrPalette.integerArray.array, copymode);
                }
                if (flagOrPalette.type == ValueType.DoubleArray)
                {
                    p.graphic.gloadPalette(x, y, w, h, ary.integerArray.array, flagOrPalette.doubleArray.array, copymode);
                }
            }
            if (ary.type == ValueType.DoubleArray)
            {
                if (flagOrPalette.type == ValueType.IntegerArray)
                {
                    p.graphic.gloadPalette(x, y, w, h, ary.doubleArray.array, flagOrPalette.integerArray.array, copymode);
                }
                if (flagOrPalette.type == ValueType.DoubleArray)
                {
                    p.graphic.gloadPalette(x, y, w, h, ary.doubleArray.array, flagOrPalette.doubleArray.array, copymode);
                }
            }
        }
        else
        {
            throw new TypeMismatch("GLOAD", 6);
        }
    }
    static void GLOAD(PetitComputer p, Value ary, Value flagOrPalette, int copymode)
    {
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("GLOAD", 1);
        }
        auto writeArea = p.graphic.writeArea[p.displaynum];
        if (writeArea.w * writeArea.h > ary.length)
        {
            throw new SubscriptOutOfRange("GLOAD", 1);
        }
        if (!flagOrPalette.isNumber && !flagOrPalette.isNumberArray)
        {
            throw new TypeMismatch("GLOAD", 2);
        }
        GLOAD(p, writeArea.x, writeArea.y, writeArea.w, writeArea.h, ary, flagOrPalette, copymode);
    }
    static void GCOPY(PetitComputer p, int srcpage, int x, int y, int x2, int y2, int x3, int y3, int cpmode)
    {
        p.graphic.gcopy(srcpage, x, y, x2, y2, x3, y3, cpmode);
    }
    static void GCOPY(PetitComputer p, int x, int y, int x2, int y2, int x3, int y3, int cpmode)
    {
        GCOPY(p, p.graphic.useGRP, x, y, x2, y2, x3, y3, cpmode);
    }
    static void GTRI(PetitComputer p, int x1, int y1, int x2, int y2, int x3, int y3, int color)
    {
        p.graphic.gtri(x1, y1, x2, y2, x3, y3, color);
    }
    static void GTRI(PetitComputer p, int x1, int y1, int x2, int y2, int x3, int y3)
    {
        p.graphic.gtri(x1, y1, x2, y2, x3, y3, p.graphic.gcolor);
    }
    static void BGMPLAY(PetitComputer p, Value numberOrMML)
    {
        if (numberOrMML.isNumber)
        {
            int number = numberOrMML.castInteger;
        }
        else if (numberOrMML.isString)
        {
            wstring mml = numberOrMML.castDString;
        }
        else
        {
            throw new TypeMismatch("BGMPLAY", 1);
        }
    }
    static void BGMPLAY(PetitComputer p, int track, int number)
    {
    }
    static void BGMPLAY(PetitComputer p, int track, int number, int volume)
    {
    }
    static void BEEP(PetitComputer p, DefaultValue!(int, false) beep, DefaultValue!(int, false) pitch, DefaultValue!(int, false) volume, DefaultValue!(int, false) pan)
    {
    }
    static void STICK(PetitComputer p, DefaultValue!(int, false) mp, out double x, out double y)
    {
        auto s = p.stick(0);
        x = s.x;
        y = s.y;
    }
    static void STICKEX(PetitComputer p, DefaultValue!(int, false) mp, out double x, out double y)
    {
        auto s = p.stick(1);
        x = s.x;
        y = s.y;
    }
    static pure nothrow int RGB(int R, int G, int B, DefaultValue!(int, false) _)
    {
        if(!_.isDefault)
        {
            //やや強引なオーバーロード
            return PetitComputer.RGB(cast(ubyte)R, cast(ubyte)G, cast(ubyte)B, cast(ubyte)_);
        }
        return PetitComputer.RGB(cast(ubyte)R, cast(ubyte)G, cast(ubyte)B);
    }
    static pure nothrow void RGBREAD(int color, out int R, out int G, out int B)
    {
        int _;
        PetitComputer.RGBRead(color, R, G, B, _);
    }
    static pure nothrow void RGBREAD(int color, out int A, out int R, out int G, out int B)
    {
        PetitComputer.RGBRead(color, R, G, B, A);
    }
    static void RANDOMIZE(PetitComputer p, int seedid)
    {
        p.random.randomize(seedid);
    }
    static void RANDOMIZE(PetitComputer p, int seedid, int seed)
    {
        p.random.randomize(seedid, seed);
    }
    static int RND(PetitComputer p, int seedid, int max)
    {
        return p.random.random(seedid, 0, max);
    }
    static double RNDF(PetitComputer p, int seedid)
    {
        return p.random.RNDF(seedid);
    }
    static int RND(PetitComputer p, int max)
    {
        return p.random.random(0, 0, max);
    }
    static double RNDF(PetitComputer p)
    {
        return p.random.RNDF(0);
    }
    @StartOptional("W")
    static void DTREAD(out int Y, out int M, out int D, out int W)
    {
        import std.datetime;
        auto currentTime = Clock.currTime();
        Y = currentTime.year;
        M = currentTime.month;
        D = currentTime.day;
        W = cast(int)currentTime.dayOfWeek;//Represents the 7 days of the Gregorian week (Sunday is 0).
    }
    @StartOptional("W")
    static void DTREAD(wstring date, out int Y, out int M, out int D, out int W)
    {
        import std.datetime;
        import std.format;
        auto v = date;
        formattedRead(v, "%d/%d/%d", &Y, &M, &D);
        W = cast(int)DateTime(Y, M, D).dayOfWeek;
    }
    static void TMREAD(out int H, out int M, out int S)
    {
        import std.datetime;
        auto currentTime = Clock.currTime();
        H = currentTime.hour;
        M = currentTime.minute;
        S = currentTime.second;
    }
    static void TMREAD(wstring time, out int H, out int M, out int S)
    {
        import std.datetime;
        import std.format;
        try
        {
            formattedRead(time, "%d:%d:%d", &H, &M, &S);
        }
        catch (Throwable)
        {
            throw new IllegalFunctionCall("TMREAD", 1);
        }
        if (!time.empty)
        {
            throw new IllegalFunctionCall("TMREAD", 1);
        }
        if (H < 0 || M < 0 || S < 0 || H > 99 || M > 99 || S > 99)
            throw new IllegalFunctionCall("TMREAD", 1);
    }
    static int LEN(Value ary)
    {
        return ary.length;
    }

    static bool tryParse(Target, Source)(ref Source p, out Target result)
        if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) &&
            isFloatingPoint!Target && !is(Target == enum))
        {
            static import core.stdc.math;
            static immutable real[14] negtab =
            [ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L,
            1e-16L,1e-8L,1e-4L,1e-2L,1e-1L,1.0L ];
            static immutable real[13] postab =
            [ 1e+4096L,1e+2048L,1e+1024L,1e+512L,1e+256L,1e+128L,1e+64L,1e+32L,
            1e+16L,1e+8L,1e+4L,1e+2L,1e+1L ];
            // static immutable string infinity = "infinity";
            // static immutable string nans = "nans";

            /*ConvException bailOut(string msg = null, string fn = __FILE__, size_t ln = __LINE__)
            {
                if (!msg)
                    msg = "Floating point conversion error";
                return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln);
            }*/
            if(p.empty) return 0;
            //enforce(!p.empty, bailOut());

            char sign = 0;                       /* indicating +                 */
            switch (p.front)
            {
                case '-':
                    sign++;
                    p.popFront();
                    if(p.empty) return 0;
                    //enforce(!p.empty, bailOut());
                    if(p.empty) return 0;
                    //enforce(!p.empty, bailOut());
                    break;
                case '+':
                    p.popFront();
                    if(p.empty) return 0;
                    //enforce(!p.empty, bailOut());
                    break;
                default: {}
            }

            bool isHex = false;
            bool startsWithZero = p.front == '0';
            if(startsWithZero)
            {
                p.popFront();
                if(p.empty)
                {
                    result = (sign) ? -0.0 : 0.0;
                    return true;
                }

                isHex = p.front == 'x' || p.front == 'X';
            }

            real ldval = 0.0;
            char dot = 0;                        /* if decimal point has been seen */
            int exp = 0;
            long msdec = 0, lsdec = 0;
            ulong msscale = 1;

            if (isHex)
            {
                int guard = 0;
                int anydigits = 0;
                uint ndigits = 0;

                p.popFront();
                while (!p.empty)
                {
                    int i = p.front;
                    while (isHexDigit(i))
                    {
                        anydigits = 1;
                        i = std.ascii.isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0';
                        if (ndigits < 16)
                        {
                            msdec = msdec * 16 + i;
                            if (msdec)
                                ndigits++;
                        }
                        else if (ndigits == 16)
                        {
                            while (msdec >= 0)
                            {
                                exp--;
                                msdec <<= 1;
                                i <<= 1;
                                if (i & 0x10)
                                    msdec |= 1;
                            }
                            guard = i << 4;
                            ndigits++;
                            exp += 4;
                        }
                        else
                        {
                            guard |= i;
                            exp += 4;
                        }
                        exp -= dot;
                        p.popFront();
                        if (p.empty)
                            break;
                        i = p.front;
                        if (i == '_')
                        {
                            p.popFront();
                            if (p.empty)
                                break;
                            i = p.front;
                        }
                    }
                    if (i == '.' && !dot)
                    {       p.popFront();
                        dot = 4;
                    }
                    else
                        break;
                }

                // Round up if (guard && (sticky || odd))
                if (guard & 0x80 && (guard & 0x7F || msdec & 1))
                {
                    msdec++;
                    if (msdec == 0)                 // overflow
                    {   msdec = 0x8000000000000000L;
                        exp++;
                    }
                }

                if(!anydigits) return 0;
                //enforce(anydigits, bailOut());
                if(!(!p.empty && (p.front == 'p' || p.front == 'P'))) return 0;
                //enforce(!p.empty && (p.front == 'p' || p.front == 'P'),
                //        bailOut("Floating point parsing: exponent is required"));
                char sexp;
                int e;

                sexp = 0;
                p.popFront();
                if (!p.empty)
                {
                    switch (p.front)
                    {   case '-':    sexp++;
                        goto case;
                    case '+':    p.popFront(); 
                        if(p.empty) return 0;
                        //enforce(!p.empty,
                        //            new ConvException("Error converting input"
                        //            " to floating point"));
                        break;
                    default: {}
                    }
                }
                ndigits = 0;
                e = 0;
                while (!p.empty && isDigit(p.front))
                {
                    if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow
                    {
                        e = e * 10 + p.front - '0';
                    }
                    p.popFront();
                    ndigits = 1;
                }
                exp += (sexp) ? -e : e;
                if(p.empty) return 0;
                //enforce(ndigits, new ConvException("Error converting input"
                //" to floating point"));

                if (msdec)
                {
                    int e2 = 0x3FFF + 63;

                    // left justify mantissa
                    while (msdec >= 0)
                    {   msdec <<= 1;
                        e2--;
                    }

                    // Stuff mantissa directly into real
                    *cast(long *)&ldval = msdec;
                    (cast(ushort *)&ldval)[4] = cast(ushort) e2;

                    // Exponent is power of 2, not power of 10
                    ldval = ldexp(ldval,exp);
                }
                goto L6;
            }
            else // not hex
            {

                bool sawDigits = startsWithZero;

                while (!p.empty)
                {
                    int i = p.front;
                    while (isDigit(i))
                    {
                        sawDigits = true;        /* must have at least 1 digit   */
                        if (msdec < (0x7FFFFFFFFFFFL-10)/10)
                            msdec = msdec * 10 + (i - '0');
                        else if (msscale < (0xFFFFFFFF-10)/10)
                        {   lsdec = lsdec * 10 + (i - '0');
                            msscale *= 10;
                        }
                        else
                        {
                            exp++;
                        }
                        exp -= dot;
                        p.popFront();
                        if (p.empty)
                            break;
                        i = p.front;
                        if (i == '_')
                        {
                            p.popFront();
                            if (p.empty)
                                break;
                            i = p.front;
                        }
                    }
                    if (i == '.' && !dot)
                    {
                        p.popFront();
                        dot++;
                    }
                    else
                    {
                        break;
                    }
                }
                if(!sawDigits) return 0;
                //enforce(sawDigits, new ConvException("no digits seen"));
            }
            if (!p.empty && (p.front == 'e' || p.front == 'E'))
            {
                char sexp;
                int e;

                sexp = 0;
                p.popFront();
                if(p.empty) return false;
                //enforce(!p.empty, new ConvException("Unexpected end of input"));
                switch (p.front)
                {   case '-':    sexp++;
                    goto case;
                case '+':    p.popFront();
                    break;
                default: {}
                }
                bool sawDigits = 0;
                e = 0;
                while (!p.empty && isDigit(p.front))
                {
                    if (e < 0x7FFFFFFF / 10 - 10)   // prevent integer overflow
                    {
                        e = e * 10 + p.front - '0';
                    }
                    p.popFront();
                    sawDigits = 1;
                }
                exp += (sexp) ? -e : e;
                if(!sawDigits) return 0;
                //enforce(sawDigits, new ConvException("No digits seen."));
            }

            ldval = msdec;
            if (msscale != 1)               /* if stuff was accumulated in lsdec */
                ldval = ldval * msscale + lsdec;
            if (ldval)
            {
                uint u = 0;
                int pow = 4096;

                while (exp > 0)
                {
                    while (exp >= pow)
                    {
                        ldval *= postab[u];
                        exp -= pow;
                    }
                    pow >>= 1;
                    u++;
                }
                while (exp < 0)
                {
                    while (exp <= -pow)
                    {
                        ldval *= negtab[u];
                        if(ldval == 0) return 0;
                        //enforce(ldval != 0, new ConvException("Range error"));
                        exp += pow;
                    }
                    pow >>= 1;
                    u++;
                }
            }
        L6: // if overflow occurred
            if(ldval == core.stdc.math.HUGE_VAL) return 0;
            //enforce(ldval != core.stdc.math.HUGE_VAL, new ConvException("Range error"));

        L1:
            result = (sign) ? -ldval : ldval;
            return true;
        }

    /// ditto
    static bool tryParse(Target, Source)(ref Source s, uint radix, out Target result)
        if (isSomeChar!(ElementType!Source) &&
            isIntegral!Target && !is(Target == enum))
    {
        if (!(radix >= 2 && radix <= 36))
        {
            result = 0;
            return false;
        }
        import core.checkedint : mulu, addu;

        immutable uint beyond = (radix < 10 ? '0' : 'a'-10) + radix;

        Target v = 0;
        bool atStart = true;

        for (; !s.empty; s.popFront())
        {
            uint c = s.front;
            if (c < '0')
                break;
            if (radix < 10)
            {
                if (c >= beyond)
                    break;
            }
            else
            {
                if (c > '9')
                {
                    c |= 0x20;//poorman's tolower
                    if (c < 'a' || c >= beyond)
                        break;
                    c -= 'a'-10-'0';
                }
            }

            bool overflow = false;
            auto nextv = v.mulu(radix, overflow).addu(c - '0', overflow);
            if (overflow || nextv > Target.max)
                goto Loverflow;
            v = cast(Target) nextv;

            atStart = false;
        }
        if (atStart)
            goto Lerr;
        result = v;
        return true;

    Loverflow:
        result = 0;
        return false;
    Lerr:
        result = 0;
        return false;
    }
    static double VAL(wstring str)
    {
        munch(str, " ");
        if(str.length > 2 && str[0..2] == "&H")
        {
            uint r;
            str = str[2..$];
            if (tryParse!(uint, wstring)(str, 16, r))
            {
                if (!str.empty)
                    return 0;
                return cast(int)r;
            }
            else
            {
                return 0;
            }
        }
        if(str.length > 2 && str[0..2] == "&B")
        {
            uint r;
            str = str[2..$];
            if (tryParse!(uint, wstring)(str, 2, r))
            {
                if (!str.empty)
                    return 0;
                return cast(int)r;
            }
            else
            {
                return 0;
            }
        }
        double val;
        if(tryParse(str, val) && str.empty)
            return val;
        else
            return 0;
    }
    static Value FLOOR(Value val)
    {
        if (!val.isNumber)
        {
            throw new TypeMismatch("FLOOR");
        }
        if (val.isInteger)
        {
            return val;
        }

        return Value(val.doubleValue.floor);
    }
    static Value ROUND(Value val)
    {
        if (!val.isNumber)
        {
            throw new TypeMismatch("ROUND");
        }
        if (val.isInteger)
        {
            return val;
        }

        return Value(val.doubleValue.round);
    }
    static Value CEIL(Value val)
    {
        if (!val.isNumber)
        {
            throw new TypeMismatch("CEIL");
        }
        if (val.isInteger)
        {
            return val;
        }

        return Value(val.doubleValue.ceil);
    }
    static wstring MID(wstring str, int i, int len)
    {
        if(i + len > str.length)
        {
            if(i >= str.length)
            {
                return "";//範囲外で空文字
            }
            return str[i..$];//iがまだ範囲内なら最後まで
        }
        //挙動未定
        return str[i..i + len];
    }
    //INSTRSUSBTLEFT
    static wstring LEFT(wstring str, int len)
    {
        if (len >= str.length)
        {
            return str;
        }
        return str[0..len];
    }
    static wstring RIGHT(wstring str, int len)
    {
        if (len >= str.length)
        {
            return str;
        }
        return str[$ - len..$];
    }
    static wstring SUBST(wstring str, int i, Value alen, DefaultValue!(Value,false) areplace)
    {
        int len = 1;
        wstring replace = "";
        if(alen.isNumber)
        {
            len = alen.castInteger;
            replace = areplace.castDString;
        }
        else
        {
            replace = alen.castDString;
            //省略されたらi以降の全文字を置換
            return str[0..i] ~ replace;
        }
        if(str.length <= i + len)
        {
            return str[0..i] ~ replace;
        }
        str.replaceInPlace(i, i + len, replace);
        return str;
    }
    static int INSTR(Value vstart, Value vstr1, DefaultValue!(wstring, false) vstr2)
    {
        int start = 0;
        wstring str1, str2;
        if(!vstr2.isDefault)
        {
            start = vstart.castInteger;
            str1 = vstr1.castDString;
            str2 = cast(wstring)vstr2;
        }
        else
        {
            str1 = vstart.castDString;
            str2 = vstr1.castDString;
        }
        ptrdiff_t index = str1[start..$].indexOf(str2);
        if (index == -1)
            return -1;
        else
            return cast(int)(index + start);
    }
    static int ASC(wstring str)
    {
        if(str.empty)
            throw new IllegalFunctionCall("ASC");
        return cast(int)str[0];
    }
    static wstring STR(double val)
    {
        return val.to!wstring;
    }
    static wstring STR(double val, int digits)
    {
        //formatだとうまくいかない
        if(digits > 63 || digits < 0)
        {
            throw new OutOfRange();
        }
        auto str = val.to!wstring;
        if (str.length >= digits)
            return str;
        wchar[64] str2;
        str2[0..digits - str.length] = ' ';
        str2[digits - str.length..digits] = str;
        return str2[0..digits].to!wstring;
    }
    /+
    HEX$(1,0)=>1
    HEX$(1)=>1
    HEX$(1,)=>1(???)
    HEX$(1,2)=>01
    +/
    static wstring HEX(int val, DefaultValue!(int, false) digits)
    {
        import std.format;
        if(digits > 8 || digits < 0)
        {
            throw new OutOfRange("HEX$", 2);
        }
        FormatSpec!char f;
        f.spec = 'X';
        f.flZero = !digits.isDefault;
        f.width = cast(int)digits;
        auto w = appender!wstring();
        formatValue(w, val, f);
        return cast(immutable)(w.data);
    }
    /+
    BIN$(1,0)=>1
    BIN$(1)=>1
    BIN$(1,)=>Type mismatch(BIN$:2)
    BIN$(1,2)=>01
    +/
    static wstring BIN(int val)
    {
        return val.to!wstring(2);
    }
    static wstring BIN(int val, int digits)
    {
        import std.format;
        if(digits > 32 || digits < 0)
        {
            throw new OutOfRange("BIN$", 2);
        }
        FormatSpec!char f;
        f.spec = 'b';
        f.flZero = true;
        f.width = digits;
        auto w = appender!wstring();
        formatValue(w, val, f);
        return cast(immutable)(w.data);
    }
    static void SPSET(PetitComputer p, int id, int defno)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isValidDef(defno))
        {
            throw new OutOfRange(smilebasicFunctionName, 2);
        }
        p.sprite.spset(id, defno);
    }
    static void SPSET(PetitComputer p, int id, int U, int V)
    {
        SPSET(p, id, U, V, 16, 16, 1);
    }
    static void SPSET(PetitComputer p, int id, int U, int V, int ATTR)
    {
        SPSET(p, id, U, V, 16, 16, ATTR);
    }
    static void SPSET(PetitComputer p, int id, int U, int V, int W, int H)
    {
        SPSET(p, id, U, V, W, H, 1);
    }
    static void SPSET(PetitComputer p, int id, int u, int v, int w, int h, int attr)
    {
        if (!p.sprite.isValidSpriteId(id))
            throw new OutOfRange(smilebasicFunctionName, 1);
        if (u < 0)
            throw new OutOfRange(smilebasicFunctionName, 2);
        if (v < 0)
            throw new OutOfRange(smilebasicFunctionName, 3);
        if (w > p.graphic.width || w < 0)
            throw new OutOfRange(smilebasicFunctionName, 4);
        if (h > p.graphic.height || h < 0)
            throw new OutOfRange(smilebasicFunctionName, 5);
        if (u + w > p.graphic.width)
            throw new OutOfRange(smilebasicFunctionName);
        if (v + h > p.graphic.height)
            throw new OutOfRange(smilebasicFunctionName);
        p.sprite.spset(id, u, v, w, h, cast(SpriteAttr)attr);
    }
    static void SPSET(PetitComputer p, int lower, int upper, int defno, out int ix)
    {
        if (!p.sprite.isValidSpriteId(upper))
            throw new OutOfRange("SPSET", 1);
        if (!p.sprite.isValidSpriteId(lower))
            throw new OutOfRange("SPSET", 2);
        if (upper < lower)
            throw new IllegalFunctionCall("SPSET", 2);
        if (!p.sprite.isValidDef(defno))
        {
            throw new OutOfRange(smilebasicFunctionName, 3);
        }
        ix = p.sprite.allocSprite(lower, upper);
        if (ix == -1)
            return;
        p.sprite.spset(ix, defno);
    }
    static void SPSET(PetitComputer p, int upper, int lower, int u, int v, out int ix)
    {
        SPSET(p, upper, lower, u, v, 16, 16, 0x01, ix);
    }
    static void SPSET(PetitComputer p, int upper, int lower, int u, int v, int w, int h, out int ix)
    {
        SPSET(p, upper, lower, u, v, w, h, 0x01, ix);
    }
    static void SPSET(PetitComputer p, int lower, int upper, int u, int v, int w, int h, int attr, out int ix)
    {
        if (!p.sprite.isValidSpriteId(upper))
            throw new OutOfRange("SPSET", 1);
        if (!p.sprite.isValidSpriteId(lower))
            throw new OutOfRange("SPSET", 2);
        if (upper < lower)
            throw new IllegalFunctionCall("SPSET", 2);
        if (u < 0)
            throw new OutOfRange(smilebasicFunctionName, 3);
        if (v < 0)
            throw new OutOfRange(smilebasicFunctionName, 4);
        if (w > p.graphic.width || w < 0)
            throw new OutOfRange(smilebasicFunctionName, 5);
        if (h > p.graphic.height || h < 0)
            throw new OutOfRange(smilebasicFunctionName, 6);
        if (u + w > p.graphic.width)
            throw new OutOfRange(smilebasicFunctionName);
        if (v + h > p.graphic.height)
            throw new OutOfRange(smilebasicFunctionName);
        ix = p.sprite.allocSprite(lower, upper);
        if (ix == -1)
            return;
        p.sprite.spset(ix, u, v, w, h, cast(SpriteAttr)attr);
    }
    static void SPSET(PetitComputer p, int defno, out int ix)
    {
        if (!p.sprite.isValidDef(defno))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        ix = p.sprite.allocSprite();
        if (ix == -1)
            return;
        p.sprite.spset(ix, defno);
    }
    static void SPSET(PetitComputer p, int u, int v, int w, int h, int attr, out int ix)
    {
        if (u < 0)
            throw new OutOfRange(smilebasicFunctionName, 1);
        if (v < 0)
            throw new OutOfRange(smilebasicFunctionName, 2);
        if (w > p.graphic.width || w < 0)
            throw new OutOfRange(smilebasicFunctionName, 3);
        if (h > p.graphic.height || h < 0)
            throw new OutOfRange(smilebasicFunctionName, 4);
        if (u + w > p.graphic.width)
            throw new OutOfRange(smilebasicFunctionName);
        if (v + h > p.graphic.height)
            throw new OutOfRange(smilebasicFunctionName);
        ix = p.sprite.allocSprite();
        if (ix == -1)
            return;
        p.sprite.spset(ix, u, v, w, h, cast(SpriteAttr)attr);
    }
    static void SPCHR(PetitComputer p, int id, int defno)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isValidDef(defno))
        {
            throw new OutOfRange(smilebasicFunctionName, 2);
        }
        p.sprite.spchr(id, defno);
    }
    static void SPCHR(PetitComputer p, int id, int U, int V)
    {
        SPCHR(p, id, U, V, 16, 16, 1);
    }
    static void SPCHR(PetitComputer p, int id, int U, int V, int ATTR)
    {
        SPCHR(p, id, U, V, 16, 16, ATTR);
    }
    static void SPCHR(PetitComputer p, int id, int U, int V, int W, int H)
    {
        SPCHR(p, id, U, V, W, H, 1);
    }
    static void SPCHR(PetitComputer p, int id, int u, int v, int w, int h, int attr)
    {
        if (!p.sprite.isValidSpriteId(id))
            throw new OutOfRange(smilebasicFunctionName, 1);
        if (!p.sprite.isSpriteDefined(id))
            throw new IllegalFunctionCall(smilebasicFunctionName, 1);
        if (u < 0)
            throw new OutOfRange(smilebasicFunctionName, 2);
        if (v < 0)
            throw new OutOfRange(smilebasicFunctionName, 3);
        if (w > p.graphic.width || w < 0)
            throw new OutOfRange(smilebasicFunctionName, 4);
        if (h > p.graphic.height || h < 0)
            throw new OutOfRange(smilebasicFunctionName, 5);
        if (u + w > p.graphic.width)
            throw new OutOfRange(smilebasicFunctionName);
        if (v + h > p.graphic.height)
            throw new OutOfRange(smilebasicFunctionName);
        p.sprite.spchr(id, u, v, w, h, cast(SpriteAttr)attr);
    }
    static void SPCHR(PetitComputer p, int id, out int defno)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isValidDef(defno))
        {
            throw new OutOfRange(smilebasicFunctionName, 2);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCHR", 1);
        }
        p.sprite.getSpchr(id, defno);
    }
    static void SPCHR(PetitComputer p, int id, out int u, out int v, out int w, out int h, out int attr)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCHR", 1);
        }
        SpriteAttr spriteattr;
        p.sprite.getSpchr(id, u, v, w, h, spriteattr);
        attr = cast(int)spriteattr;
    }
    static void SPCHR(PetitComputer p, int id, out int u, out int v)
    {
        int w, h, attr;
        SPCHR(p, id, u, v, w, h , attr);
    }
    static void SPCHR(PetitComputer p, int id, out int u, out int v, out int attr)
    {
        int w, h;
        SPCHR(p, id, u, v, w, h , attr);
    }
    static void SPCHR(PetitComputer p, int id, out int u, out int v, out int w, out int h)
    {
        int attr;
        SPCHR(p, id, u, v, w, h , attr);
    }
    static void SPHIDE(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPHIDE", 1);
        }
        p.sprite.sphide(id);
    }
    static void SPSHOW(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPSHOW", 1);
        }
        p.sprite.spshow(id);
    }
    static void SPOFS(PetitComputer p, int id, DefaultValue!double x, DefaultValue!double y, double z)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPOFS", 1);
        }
        if (x.isDefault || y.isDefault)
        {
            double x_, y_, z_;
            p.sprite.getspofs(id, x_, y_, z_);
            x.setDefaultValue(x_);
            y.setDefaultValue(y_);
        }
        p.sprite.spofs(id, cast(double)x, cast(double)y, z);
    }
    static void SPOFS(PetitComputer p, int id, DefaultValue!double x, DefaultValue!double y)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPOFS", 1);
        }
        if (x.isDefault || y.isDefault)
        {
            double x_, y_, z_;
            p.sprite.getspofs(id, x_, y_, z_);
            x.setDefaultValue(x_);
            y.setDefaultValue(y_);
        }
        p.sprite.spofs(id, cast(double)x, cast(double)y);
    }
    @StartOptional("z")
    static void SPOFS(PetitComputer p, int id, out double x, out double y, out double z)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPOFS", 1);
        }
        p.sprite.getspofs(id, x, y, z);
    }
    static void SPANIM(PetitComputer p, Value[] va_args)
    {
        //TODO:配列
        auto args = retro(va_args);
        int no = args[0].castInteger;
        if (!p.sprite.isValidSpriteId(no))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(no))
        {
            throw new IllegalFunctionCall("SPANIM", 1);
        }
        double[] animdata;
        if(args[2].isString)
        {
            VM vm = p.vm;
            vm.restoreData(args[2].castDString);
            int keyframe = vm.readData.castInteger;
            auto target = p.sprite.getSpriteAnimTarget(args[1].castDString);
            int item = 2;
            if((target & 7) == SpriteAnimTarget.XY || (target & 7) == SpriteAnimTarget.UV) item++;
            animdata = new double[item * keyframe + 1];
            int j;
            for(int i = 0; i < keyframe; i++)
            {
                animdata[j] = vm.readData.castDouble;
                animdata[j + 1] = vm.readData.castDouble;
                if(item == 3)
                    animdata[j + 2] = vm.readData.castDouble;
                j += item;
            }
            if(args.length > 3)
                animdata[j] = args[3].castInteger;
            else
                animdata[j] = 1;//loop count
        }
        else
        {
            int i;
            animdata = new double[args.length - 2];
            foreach(a; args[2..$])
            {
                animdata[i++] = a.castDouble;
            }
        }
        if(args[1].isString)
            p.sprite.spanim(no, args[1].castDString, animdata);
        if(args[1].isNumber)
            p.sprite.spanim(no, cast(SpriteAnimTarget)(args[1].castInteger), animdata);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        int dummy;
        p.sprite.getspdef(id, U, V, dummy, dummy, dummy, dummy, dummy);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V, out int A)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        int dummy;
        p.sprite.getspdef(id, U, V, dummy, dummy, dummy, dummy, A);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V, out int W, out int H)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        int dummy;
        p.sprite.getspdef(id, U, V, W, H, dummy, dummy, dummy);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V, out int W, out int H, out int A)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        int dummy;
        p.sprite.getspdef(id, U, V, W, H, dummy, dummy, A);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V, out int W, out int H, out int HX, out int HY)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        int dummy;
        p.sprite.getspdef(id, U, V, W, H, HX, HY, dummy);
    }
    static void SPDEF(PetitComputer p, int id, out int U, out int V, out int W, out int H, out int HX, out int HY, out int A)
    {
        if (!p.sprite.isValidDef(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        p.sprite.getspdef(id, U, V, W, H, HX, HY, A);
    }
    static void SPDEF(PetitComputer p, Value[] va_args2)
    {
        auto va_args = retro(va_args2);
        switch(va_args.length)
        {
            case 0:
                p.sprite.spdef();//初期化
                return;
            case 1://array
                {
                    if(va_args[0].isNumberArray)
                    {
                        writeln("NOTIMPL:SPDEF ARRAY");
                        //return;
                    }
                    if(va_args[0].isString)
                    {
                        VM vm = p.vm;
                        vm.pushDataIndex();
                        vm.restoreData(va_args[0].castDString);
                        auto count = vm.readData().castInteger;//読み込むスプライト数
                        int defno = 0;//?
                        for(int i = 0; i < count; i++)
                        {
                            int U = vm.readData().castInteger;
                            int V = vm.readData().castInteger;
                            int W = vm.readData().castInteger;
                            int H = vm.readData().castInteger;
                            int HX = vm.readData().castInteger;
                            int HY = vm.readData().castInteger;
                            int ATTR = vm.readData().castInteger;
                            p.sprite.SPDEFTable[defno] = SpriteDef(U, V, W, H, HX, HY, cast(SpriteAttr)ATTR);
                            defno++;
                        }
                        vm.popDataIndex();
                        return;
                    }
                    throw new IllegalFunctionCall("SPDEF");
                }
            default:
        }
        {
            int defno = va_args[0].castInteger;
            if (!p.sprite.isValidDef(defno))
            {
                throw new OutOfRange(smilebasicFunctionName, 1);
            }
            int U = va_args[1].castInteger;
            int V = va_args[2].castInteger;
            int W = 16, H = 16, HX = 0, HY = 0, ATTR = 1;
            if (va_args.length == 4)
            {
                ATTR = va_args[3].castInteger;
            }
            else if (va_args.length == 5)
            {
                W = va_args[3].castInteger;
                H = va_args[4].castInteger;
            }
            else if (va_args.length == 6)
            {
                W = va_args[3].castInteger;
                H = va_args[4].castInteger;
                ATTR = va_args[5].castInteger;
            }
            else if (va_args.length == 7)
            {
                W = va_args[3].castInteger;
                H = va_args[4].castInteger;
                HX = va_args[5].castInteger;
                HY = va_args[6].castInteger;
            }
            else if (va_args.length == 8)
            {
                W = va_args[3].castInteger;
                H = va_args[4].castInteger;
                HX = va_args[5].castInteger;
                HY = va_args[6].castInteger;
                ATTR = va_args[7].castInteger;
            }
            else
            {
                throw new IllegalFunctionCall("SPDEF");
            }
            p.sprite.SPDEFTable[defno] = SpriteDef(U, V, W, H, HX, HY, cast(SpriteAttr)ATTR);
        }
    }
    static void SPCLR(PetitComputer p, DefaultValue!(int, false) i)
    {
        //spset is not checked
        if(i.isDefault)
            p.sprite.spclr();
        else
        {
            if (!p.sprite.isValidSpriteId(cast(int)i))
            {
                throw new OutOfRange(smilebasicFunctionName, 1);
            }
            p.sprite.spclr(cast(int)i);
        }
    }
    static void SPHOME(PetitComputer p, int i, int hx, int hy)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPHOME", 1);
        }
        p.sprite.sphome(i, hx, hy);
    }
    static void SPHOME(PetitComputer p, int i, out int hx, out int hy)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPHOME", 1);
        }
        p.sprite.getsphome(i, hx, hy);
    }
    static void SPSCALE(PetitComputer p, int i, double x, double y)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPSCALE", 1);
        }
        p.sprite.spscale(i, x, y);
    }
    static void SPSCALE(PetitComputer p, int i, out double x, out double y)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPSCALE", 1);
        }
        p.sprite.getspscale(i, x, y);
    }
    static void SPROT(PetitComputer p, int i, double rot)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPROT", 1);
        }
        p.sprite.sprot(i, rot);
    }
    static void SPROT(PetitComputer p, int i, out double rot)
    {
        if (!p.sprite.isValidSpriteId(i))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(i))
        {
            throw new IllegalFunctionCall("SPROT", 1);
        }
        p.sprite.getsprot(i, rot);
    }
    static void SPCOLOR(PetitComputer p, int id, int color)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOLOR", 1);
        }
        p.sprite.spcolor(id, cast(uint)color);
    }
    static void SPCOLOR(PetitComputer p, int id, out int color)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOLOR", 1);
        }
        p.sprite.getspcolor(id, color);
    }
    static void SPLINK(PetitComputer p, int child, int parent)
    {
        if (!p.sprite.isValidSpriteId(child))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isValidSpriteId(parent))
        {
            throw new OutOfRange(smilebasicFunctionName, 2);
        }
        p.sprite.splink(child, parent);
    }
    static int SPLINK(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange("SPLINK", 1);
        }
        return p.sprite.splink(id);
    }
    static void SPUNLINK(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPUNLINK", 1);
        }
        p.sprite.spunlink(id);
    }
    static void SPCOL(PetitComputer p, int id, DefaultValue!(int, false) scale)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        scale.setDefaultValue(false);
        p.sprite.spcol(id, cast(bool)scale);
    }
    static void SPCOL(PetitComputer p, int id, DefaultValue!int scale, int mask)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        scale.setDefaultValue(false);
        p.sprite.spcol(id, cast(bool)scale, mask);
    }
    static void SPCOL(PetitComputer p, int id, int x, int y, int w, int h, int scale)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        p.sprite.spcol(id, cast(short)x, cast(short)y, cast(ushort)w, cast(ushort)h, cast(bool)scale, -1);
    }
    static void SPCOL(PetitComputer p, int id, int x, int y, int w, int h, DefaultValue!int scale, int mask)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        scale.setDefaultValue(false);
        p.sprite.spcol(id, cast(short)x, cast(short)y, cast(ushort)w, cast(ushort)h, cast(bool)scale, mask);
    }
    static void SPCOL(PetitComputer p, int id, int x, int y, int w, int h, DefaultValue!int scale)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        scale.setDefaultValue(false);
        p.sprite.spcol(id, cast(short)x, cast(short)y, cast(ushort)w, cast(ushort)h, cast(bool)scale, -1);
    }
    static void SPCOL(PetitComputer p, int id, int x, int y, int w, int h)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPCOL", 1);
        }
        p.sprite.spcol(id, cast(short)x, cast(short)y, cast(ushort)w, cast(ushort)h, false, -1);
    }
    static int SPHITSP(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        //spset is not checked
        return p.sprite.sphitsp(id);
    }
    static int SPHITSP(PetitComputer p, int id, int min)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        return p.sprite.sphitsp(id, min, 511);//?
    }
    static int SPHITSP(PetitComputer p, int id, int min, int max)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        return p.sprite.sphitsp(id, min, max);
    }
    static void SPVAR(PetitComputer p, int id, int var, double val)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        //spset is not checked
        p.sprite.spvar(id, var, val);
    }
    static double SPVAR(PetitComputer p, int id, int var)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        return p.sprite.spvar(id, var);
    }
    static int SPCHK(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        return p.sprite.spchk(id);
    }
    static void SPPAGE(PetitComputer p, int page)
    {
        if (!p.isValidGraphicPage(page))
        {
            throw new OutOfRange("SPPAGE", 1);
        }
        p.sprite.sppage[p.displaynum] = page;
    }
    static int SPPAGE(PetitComputer p)
    {
        return p.sprite.sppage[p.displaynum];
    }
    static void SPCLIP(PetitComputer p)
    {
        p.sprite.spclip;
    }
    static void SPCLIP(PetitComputer p, int x1, int y1, int x2, int y2)
    {
        p.sprite.spclip(x1, y1, x2, y2);
    }
    static int SPUSED(PetitComputer p, int id)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange("SPUSED", 1);
        }
        return p.sprite.spused(id);
    }
    static void SPFUNC(PetitComputer p, int id, wstring func)
    {
        if (!p.sprite.isValidSpriteId(id))
        {
            throw new OutOfRange("SPFUNC", 1);
        }
        /*
        if (!p.sprite.isSpriteDefined(id))
        {
            throw new IllegalFunctionCall("SPFUNC", 1);
        }*/
        auto callback = p.vm.createCallback(func);
        if (callback.type == CallbackType.none)
            throw new IllegalFunctionCall("SPFUNC"/*, 2*/);
        p.sprite.spfunc(id, callback);
    }
    static void BGMSTOP(PetitComputer p)
    {
        writeln("NOTIMPL:BGMSTOP");
    }
    static int BGMCHK(PetitComputer p)
    {
        writeln("NOTIMPL:BGMCHK");
        return false;
    }
    static int CHKCHR(PetitComputer p, int x, int y)
    {
        return cast(int)(p.console.console[p.displaynum][y][x].character);
    }
    struct FixedBuffer(T, size_t S)
    {
        T[S] buffer = void;
        size_t length;
        void put(T v)
        {
            if (buffer.length <= length)
            {
                throw new StringTooLong("FORMAT$", 2);
            }
            buffer[length++] = v;
        }
    }
    static wstring FORMAT(PetitComputer p, Value[] va_args)
    {
        alias retro!(Value[]) VaArgs;
        auto args = retro(va_args);
        auto format = args[0].castDString;
        import std.array : appender;
        import std.format;
        FixedBuffer!(wchar, 1024) buffer;//String too long
        int j = 1;
        for(int i = 0; i < format.length; i++)
        {
            auto f = format[i];
            if(f == '%')
            {
                i = i + 1;
                bool sign = false;//+
                bool left = false;//-
                bool space = false;//' '
                bool zero = false;
                for(; i < format.length; i++)
                {
                    auto c = format[i];
                    if (c == '+')
                    {
                        sign = true;
                    }
                    else if (c == ' ')
                    {
                        space = true;
                    }
                    else if (c == '-')
                    {
                        left = true;
                    }
                    else if (c == '0')
                    {
                        zero = true;
                    }
                    else
                    {
                        break;
                    }
                }
                int d1, d2 = 6;
                bool d2f;
                wstring a1 = format[i..$];
                if (a1[0] >= '0' && a1[0] <= '9')
                {
                    d1 = parse!(int, wstring)(a1);
                }
                if (a1[0] == '.')
                {
                    a1 = a1[1..$];
                    if (a1[0] >= '0' && a1[0] <= '9')
                    {
                        d2 = parse!(int, wstring)(a1);
                        d2f = true;
                    }
                }
                wstring buf;
                FormatSpec!wchar spec;
                switch (a1[0])
                {
                    case 'S', 's':
                        {
                            auto val = args[j].castDString;
                            spec.width = d1;
                            spec.flDash = left;
                            spec.flZero = zero;
                            spec.flPlus = sign;
                            spec.flSpace = space;
                            formatValue(&buffer, val, spec);
                            break;
                        }
                    case 'X', 'x':
                        spec.spec = 'X';
                        goto caseInteger;
                    case 'B', 'b':
                        spec.spec = 'b';
                        goto caseInteger;
                    case 'D', 'd':
                        spec.spec = 'd';
                        caseInteger:
                        {
                            auto val = args[j].castInteger;
                            spec.width = d1;
                            if (d2f)
                                spec.precision = d2;
                            spec.flDash = left;
                            spec.flZero = zero;
                            spec.flPlus = sign;
                            spec.flSpace = space;
                            formatValue(&buffer, val, spec);
                            break;
                        }
                    case 'F', 'f':
                        {
                            spec.spec = 'f';
                            auto val = args[j].castDouble;
                            spec.width = d1;
                            if (d2 < 1022)
                            {
                                spec.precision = d2;
                                spec.flDash = left;
                                spec.flZero = zero;
                                spec.flPlus = sign;
                                spec.flSpace = space;
                                formatValue(&buffer, val, spec);
                            }
                            break;
                        }
                    default:
                        throw new IllegalFunctionCall("FORMAT$");
                }
                j++;
                i = 0;
                format = a1;
                continue;
            }
            buffer.put(f);
        }
        wchar[] data = new wchar[buffer.length];
        data[] = buffer.buffer[0..buffer.length];
        return cast(immutable)data;
    }
    static wstring CHR(int code)
    {
        return (cast(wchar)code).to!wstring;
    }
    static pure nothrow double POW(double a1, double a2)
    {
        return a1 ^^ a2;
    }
    static double SQR(double a1)
    {
        if (a1 < 0)
        {
            throw new OutOfRange();
        }
        return sqrt(a1);
    }
    //GalateaTalk利用面倒くさい...
    static void TALK(wstring a1)
    {
    }
    static void BGCLR(PetitComputer p, DefaultValue!(int, false) layer)
    {
        if(layer.isDefault)
        {
            foreach(bg; p.allBG)
            {
                bg.clear;
            }
            return;
        }
        if (!p.isValidLayer(cast(int)layer))
        {
            throw new OutOfRange("BGCLR", 1);
        }
        p.getBG(cast(int)layer).clear;
    }
    static void BGSCREEN(PetitComputer p, int layer, int w, int h)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGSCREEN", 1);
        }
        p.getBG(layer).screen(w, h);
    }
    static void BGOFS(PetitComputer p, int layer, int x, int y, DefaultValue!(int, false) z)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGOFS", 1);
        }
        z.setDefaultValue(p.getBG(layer).offsetz);
        p.getBG(layer).ofs(x, y, cast(int)z);
    }
    static void BGCLIP(PetitComputer p, int layer)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGCLIP", 1);
        }
        p.getBG(layer).clip();
    }
    static void BGCLIP(PetitComputer p, int layer, int x, int y, int x2, int y2)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGCLIP", 1);
        }
        p.getBG(layer).clip(x, y, x2, y2);
    }
    static void BGPUT(PetitComputer p, int layer, int x, int y, int screendata)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGPUT", 1);
        }
        if (x < 0 || x >= p.getBG(layer).width)
        {
            throw new OutOfRange("BGPUT", 2);
        }
        if (y < 0 || y >= p.getBG(layer).height)
        {
            throw new OutOfRange("BGPUT", 3);
        }
        p.getBG(layer).put(x, y, screendata);
    }
    static void BGHOME(PetitComputer p, int layer, int x, int y)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGHOME", 1);
        }
        p.getBG(layer).home(x, y);
    }
    static void BGSCALE(PetitComputer p, int layer, double x, double y)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGSCALE", 1);
        }
        p.getBG(layer).scale(x, y);
    }
    static void BGSCALE(PetitComputer p, int layer, out double x, out double y)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGSCALE", 1);
        }
        p.getBG(layer).getScale(x, y);
    }
    static void BGROT(PetitComputer p, int layer, int rot)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGROT", 1);
        }
        p.getBG(layer).rot(rot);
    }
    static int BGROT(PetitComputer p, int layer)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange(smilebasicFunctionName, 1);
        }
        return p.getBG(layer).rot;
    }
    static void BGFILL(PetitComputer p, int layer, int x, int y, int x2, int y2, int screendata)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGFILL", 1);
        }
        p.getBG(layer).fill(x, y, x2, y2, screendata);
    }
    static void BGPAGE(PetitComputer p, int page)
    {
        if (!p.isValidGraphicPage(page))
        {
            throw new OutOfRange("BGPAGE", 1);
        }
        p.bgpage[p.displaynum] = page;
    }
    static int BGPAGE(PetitComputer p)
    {
        return p.bgpage[p.displaynum];
    }
    static void BGSHOW(PetitComputer p, int layer)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGSHOW", 1);
        }
        p.getBG(layer).show = true;
    }
    static void BGHIDE(PetitComputer p, int layer)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGHIDE", 1);
        }
        p.getBG(layer).show = false;
    }
    static int BGGET(PetitComputer p, int layer, int x, int y)
    {
        return BGGET(p, layer, x, y, 0);
    }
    static int BGGET(PetitComputer p, int layer, int x, int y, int flag)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGGET", 1);
        }
        return p.getBG(layer).get(x, y, flag);
    }
    static void BGSAVE(PetitComputer p, int layer, int x, int y, int w, int h, Value ary)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGSAVE", 1);
        }
        if (w < 1)
        {
            throw new OutOfRange("BGSAVE", 4);
        }
        if (h < 1)
        {
            throw new OutOfRange("BGSAVE", 4);
        }
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("BGSAVE", 6);
        }
        if (ary.type == ValueType.IntegerArray)
        {
            if (w * h > ary.length)
                ary.integerArray.length = w * h;
            p.getBG(layer).save(x, y, w, h, ary.integerArray.array);
        }
        if (ary.type == ValueType.DoubleArray)
        {
            if (w * h > ary.length)
                ary.doubleArray.length = w * h;
            p.getBG(layer).save(x, y, w, h, ary.doubleArray.array);
        }
        
    }
    static void BGSAVE(PetitComputer p, int layer, Value ary)
    {
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("BGSAVE", 2);
        }
        BGSAVE(p, layer, 0, 0, p.getBG(layer).width, p.getBG(layer).height, ary);
    }
    static void BGLOAD(PetitComputer p, int layer, int x, int y, int w, int h, Value ary, int bgcharoffset)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGLOAD", 1);
        }
        if (w < 1)
        {
            throw new OutOfRange("BGLOAD", 4);
        }
        if (h < 1)
        {
            throw new OutOfRange("BGLOAD", 4);
        }
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("BGLOAD", 6);
        }
        if (ary.type == ValueType.IntegerArray)
        {
            if (w * h > ary.length)
                ary.integerArray.length = w * h;
            p.getBG(layer).load(x, y, w, h, ary.integerArray.array, bgcharoffset);
        }
        if (ary.type == ValueType.DoubleArray)
        {
            if (w * h > ary.length)
                ary.doubleArray.length = w * h;
            p.getBG(layer).load(x, y, w, h, ary.doubleArray.array, bgcharoffset);
        }

    }
    static void BGLOAD(PetitComputer p, int layer, int x, int y, int w, int h, Value ary)
    {
        BGLOAD(p, layer, x, y, w, h, ary, 0);
    }
    static void BGLOAD(PetitComputer p, int layer, Value ary)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGLOAD", 1);
        }
        if (!ary.isNumberArray)
        {
            throw new TypeMismatch("BGLOAD", 2);
        }
        BGLOAD(p, layer, 0, 0, p.getBG(layer).width, p.getBG(layer).height, ary, 0);
    }
    static void BGFUNC(PetitComputer p, int layer, wstring func)
    {
        if (!p.isValidLayer(layer))
        {
            throw new OutOfRange("BGFUNC", 1);
        }
        auto callback = p.vm.createCallback(func);
        if (callback.type == CallbackType.none)
            throw new IllegalFunctionCall("BGUNC"/*, 2*/);
        p.getBG(layer).callback = callback;
    }
    static void EFCON()
    {
    }
    static void EFCOFF()
    {
    }
    static void EFCSET(Value[])
    {
    }
    static void EFCWET(Value[])
    {
    }
    static void COPY(PetitComputer p, Value[] rawargs)
    {
        auto args = retro(rawargs);
        //文字列はリテラル渡すとType mismatch
        if(args.length > 5 || args.length < 2)
        {
            throw new IllegalFunctionCall("COPY");
        }
        if (!args[0].isArray)
        {
            throw new TypeMismatch("COPY", 1);
        }
        //COPY string, string->文字列COPY
        //COPY array, string->DATA COPY
        Value dst = args[0];
        int dstoffset = 0;
        int sourceArrayIndex = 1;
        if (!args[1].isArray)
        {
            if (!args[1].isNumber)
                throw new TypeMismatch("COPY", 2);
            dstoffset = args[1].castInteger;
            sourceArrayIndex = 2;
        }

        int srcoffset = 0;
        int len = dst.length - dstoffset;//省略時はコピー元の末尾まで
        if(args[sourceArrayIndex].isString && !args[0].isString)
        {
            if (args.length > sourceArrayIndex + 2)
            {
                throw new SyntaxError();//syntax error?why???
            }
            if (args.length == sourceArrayIndex + 2)
            {
                if (!args[sourceArrayIndex + 1].isNumber)
                    throw new TypeMismatch("COPY", sourceArrayIndex + 2);
                len = args[sourceArrayIndex + 1].castInteger;
            }

            //DATAから
            VM vm = p.vm;
            vm.restoreData(args[sourceArrayIndex].castDString);
            for(int i = 0; i < len; i++)
            {
                Value data = vm.readData();
                dst[dstoffset++] = data;
            }
            return;
        }
        if (!args[sourceArrayIndex].isArray)
        {
            throw new TypeMismatch("COPY", sourceArrayIndex + 1);
        }
        Value src = args[sourceArrayIndex];
        if (args.length > sourceArrayIndex + 3)
        {
            throw new SyntaxError();//syntax error?why???
        }
        if (args.length == sourceArrayIndex + 3)
        {
            if (!args[sourceArrayIndex + 1].isNumber)
                throw new TypeMismatch("COPY", sourceArrayIndex + 2);
            srcoffset = args[sourceArrayIndex + 1].castInteger;
            if (!args[sourceArrayIndex + 2].isNumber)
                throw new TypeMismatch("COPY", sourceArrayIndex + 3);
            len = args[sourceArrayIndex + 2].castInteger;
        }
        else if (args.length == sourceArrayIndex + 2)
        {
            if (!args[sourceArrayIndex + 1].isNumber)
                throw new TypeMismatch("COPY", sourceArrayIndex + 2);
            len = args[sourceArrayIndex + 1].castInteger;
        }
        else
        {
            len = src.length - srcoffset;
        }
        if (dst.length < len + dstoffset)
        {
            if (dst.dimCount != 1)
            {
                throw new SubscriptOutOfRange("COPY");
            }
            dst.length = len + dstoffset;
        }
        if (src.type == ValueType.StringArray && dst.type == ValueType.StringArray)
        {
            for(int i = 0; i < len; i++)
            {
                dst.stringArray.array[i + dstoffset] = src.stringArray.array[i + srcoffset].dup;
            }
        }
        else
        {
            for(int i = 0; i < len; i++)
            {
                dst[i + dstoffset] = src[i + srcoffset];
            }
        }
    }
    @BasicName("LOAD")
    static Value LOAD1(PetitComputer p, wstring name, DefaultValue!(int, false) flag)
    {
        import otya.smilebasic.project;
        flag.setDefaultValue(0);
        auto type = Projects.splitResourceName(name);
        wstring txt;
        wstring resname = type[0];
        wstring projectname = type[1];
        wstring filename = type[2];
        if (!Projects.isValidFileName(filename))
        {
            throw new IllegalFunctionCall("LOAD");
        }
        if(projectname != "" && projectname.toUpper != "SYS")
        {
            throw new IllegalFunctionCall("LOAD");
        }
        if(projectname == "")
        {
            projectname = p.project.currentProject;
        }
        if(resname == "TXT")
        {
            if(p.project.loadFile(projectname, resname, filename, txt))
            {
                return Value(txt);
            }
            //not exist
            return Value("");
        }
        throw new IllegalFunctionCall("LOAD");
    }
    //LOAD"TXT:HOGE",ARRAY Type mismatch(LOAD:2)
    //LOAD"TXT:HOGE",STR$ Type mismatch(LOAD:2)
    //LOAD"DAT:HOGE",STR$ Type mismatch(LOAD:2)
    static void LOAD(PetitComputer p, wstring name)
    {
        LOAD(p, name, Value(0));
    }
    static void LOAD(PetitComputer p, wstring name, Value data)
    {
        void LOAD2(PetitComputer p, wstring name, int flag)
        {
            import otya.smilebasic.project;
            auto type = Projects.splitResourceName(name);
            wstring txt;
            wstring resname = type[0].toUpper;
            wstring projectname = type[1];
            wstring filename = type[2];
            if (!Projects.isValidFileName(filename))
            {
                throw new IllegalFunctionCall("LOAD");
            }
            if(projectname != "" && projectname.toUpper != "SYS")
            {
                throw new IllegalFunctionCall("LOAD");
            }
            if(projectname == "")
            {
                projectname = p.project.currentProject;
            }
            if(resname == "" || resname.indexOf("PRG") == 0)
            {
                int lot = 0/*always 0...?*/;
                if(resname != "" && resname != "PRG")
                {
                    auto num = resname[3..$];
                    lot = num.to!int;
                }
                if(!p.project.loadFile(projectname, "TXT", filename, txt))
                {
                    //not exist
                    return;
                }
                p.program.slot[lot].load(filename, txt);
                return;
            }
            if(resname.indexOf("GRP") == 0)
            {
                throw new IllegalFunctionCall("NOTIMPL:LOAD GRP");
            }
            throw new IllegalFunctionCall("LOAD");
        }
        if (data.isNumber)
        {
            LOAD2(p, name, data.castInteger);
            return;
        }
        LOAD(p, name, data, 0);
    }
    static void LOAD(PetitComputer p, wstring name, Value data, int flag)
    {
        auto file = Projects.parseFileName(name);
        if (file.resource != Resource.data)
        {
            throw new IllegalFunctionCall("LOAD", 2);
        }
        if (data.isNumberArray)
        {
            if (data.type == ValueType.IntegerArray)
                p.project.loadDataFile(file, data.integerArray);
            else if (data.type == ValueType.DoubleArray)
                p.project.loadDataFile(file, data.doubleArray);
            else
                throw new TypeMismatch("LOAD", 2);
        }
        else
        {
            throw new TypeMismatch("LOAD", 2);
        }
    }
    static void SAVE(PetitComputer p, wstring name)
    {
        auto file = Projects.parseFileName(name);
        if (file.resource != Resource.program && file.resource != Resource.none)
        {
            throw new IllegalFunctionCall("SAVE"/*, 1*/);
        }
        auto slot = 0/*always 0...?*/;
        if (file.hasResourceNumber)
        {
            slot = file.resourceNumber;
        }
        if (slot >= p.program.slotSize)
        {
            throw new IllegalFunctionCall("SAVE"/*, 1*/);
        }
        auto str = cast(immutable)p.program.slot[slot].text;
        p.project.saveTextFile(file, str);
    }
    static void SAVE(PetitComputer p, wstring name, Value data)
    {
        auto file = Projects.parseFileName(name);
        if (file.resource != Resource.data && file.resource != Resource.text)
        {
            throw new IllegalFunctionCall("SAVE"/*, 1*/);
        }
        if (file.resource == Resource.text && data.isString)
        {
            p.project.saveTextFile(file, data.castDString);
        }
        else if (file.resource == Resource.data && data.isNumberArray)
        {
            if (data.type == ValueType.IntegerArray)
                p.project.saveDataFile(file, data.integerArray);
            else if (data.type == ValueType.DoubleArray)
                p.project.saveDataFile(file, data.doubleArray);
            else
                throw new TypeMismatch("SAVE", 2);
        }
        else
        {
            throw new TypeMismatch("SAVE", 2);
        }
    }
    static void PROJECT(PetitComputer p, wstring name)
    {
        //DirectMode only
        if(p.isRunningDirectMode)
        {
            if(!p.project.isValidProjectName(name))
            {
                throw new IllegalFunctionCall("PROJECT");
            }
            p.project.currentProject = name;
            return;
        }
        throw new CantUseInProgram("PROJECT");
    }
    static void PROJECT(PetitComputer p, out Array!wchar name)
    {
        name = new Array!wchar(cast(wchar[])p.project.currentProject.dup);
    }
    //FILES TYPE$
    //((TXT|DAT):)?\w+/?
    static void FILES(PetitComputer p)
    {
        FILES(p, Value(cast(wchar[])p.project.currentProject));
    }
    static void FILES(PetitComputer p, Value nameOrArray)
    {
        import otya.smilebasic.project;
        if(nameOrArray.isString)
        {
            auto t = Projects.splitResourceName(nameOrArray.castDString);
            auto res = t[0];
            if(t[1].length && t[2].length)
            {
                throw new IllegalFunctionCall("FILES");
            }
            if(t[2].length && nameOrArray.castDString.indexOf("/") != -1)
            {
                //"TXT:A":OK
                //"TXT:A/":OK
                //"TXT:/A":X
                //"TXT:A/A":X
                throw new IllegalFunctionCall("FILES");
            }
            auto project = t[1].length ? t[1] : t[2];
            //project:.->hidden project
            //project:/->project list
            auto l = p.project.getFileList(project, res);
            p.console.print(res, "\t", project, "\n");
            foreach(i; l)
            {
                p.console.print(i, "\n");
            }
        }
        else if (nameOrArray.type == ValueType.StringArray)
        {
            FILES(p, p.project.currentProject, nameOrArray);
        }
        else
        {
            throw new TypeMismatch("FILES"/*, 1*/);
        }
    }
    static void FILES(PetitComputer p, wstring name, Value array)
    {
        import otya.smilebasic.project;
        if (array.type != ValueType.StringArray)
        {
            throw new TypeMismatch("FILES"/*, 1*/);
        }
        auto t = Projects.splitResourceName(name);
        auto res = t[0];
        if(t[1].length && t[2].length)
        {
            throw new IllegalFunctionCall("FILES");
        }
        if(t[2].length && name.indexOf("/") != -1)
        {
            throw new IllegalFunctionCall("FILES");
        }
        auto project = t[1].length ? t[1] : t[2];
        auto l = p.project.getFileList(project, res);
        if (l.length > array.length)
        {
            array.length = cast(int)l.length;
        }
        foreach (i, j; l)
        {
            array[cast(int)i] = Value(j);
        }
    }
    static int CHKFILE(PetitComputer p, wstring file)
    {
        return p.project.chkfile(file);
    }
    static void ACLS(PetitComputer p)
    {
        p.acls(true, true, true);
    }
    static void ACLS(PetitComputer p, int gr, int sp, int fn)
    {
        p.acls(cast(bool)gr, cast(bool)sp, cast(bool)fn);
    }
    static int CHKCALL(PetitComputer p, wstring func)
    {
        func = func.toUpper;
        return p.vm.chkcall(func);
    }
    static int CHKLABEL(PetitComputer p, wstring label)
    {
        label = label.toUpper;
        return p.vm.chklabel(label);
    }
    static int CHKVAR(PetitComputer p, wstring var)
    {
        var = var.toUpper;
        return p.vm.chkvar(var);
    }
    static wstring INKEY(PetitComputer p)
    {
        return p.inkey();
    }
    static auto getSortArgument(Value[] arg, out int start, out int count)
    {
        auto args = retro(arg);
        //引数何も指定しなくても実行前エラーは出ない
        if (args.length < 1)
            throw new IllegalFunctionCall("");
        if (args.length > 2)
        {
            if (args[0].isNumber || args[1].isNumber)
            {
                if (args[0].isNumber && args[1].isNumber)
                {
                    start = args[0].castInteger();
                    count = args[1].castInteger();
                    args = args[2..$];
                }
                else
                {
                    throw new IllegalFunctionCall("");
                }
            }
            else
            {
                start = 0;
                count = args[0].length;
            }
        }
        else
        {
            start = 0;
            count = args[0].length;
        }
        if (args.length > 8)
            throw new IllegalFunctionCall("");
        foreach (ref a; args)
        {
            if (a.isString || !a.isArray)
            {
                throw new TypeMismatch();
            }
        }
        return args;
    }
    struct wrappeeer
    {
        Value value;
        union
        {
            int[] integerArray;
            double[] doubleArray;
            Array!wchar[] stringArray;
        }
        this(ref Value v)
        {
            value = v;
            if (value.type == ValueType.IntegerArray)
            {
                integerArray = value.integerArray.array;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray = value.doubleArray.array;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray = value.stringArray.array;
            }
        }
        private void slice(int x, int y)
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray = integerArray[x..y];
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray = doubleArray[x..y];
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray = stringArray[x..y];
            }
        }
        size_t length()
        {
            if (value.type == ValueType.IntegerArray)
            {
                return integerArray.length;
            }
            if (value.type == ValueType.DoubleArray)
            {
                return doubleArray.length;
            }
            if (value.type == ValueType.StringArray)
            {
                return stringArray.length;
            }
            throw new TypeMismatch();
        }
        bool empty()
        {
            if (value.type == ValueType.IntegerArray)
            {
                return integerArray.empty;
            }
            if (value.type == ValueType.DoubleArray)
            {
                return doubleArray.empty;
            }
            if (value.type == ValueType.StringArray)
            {
                return stringArray.empty;
            }
            throw new TypeMismatch();
        }
        void popFront()
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray.popFront;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray.popFront;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray.popFront;
            }
        }
        void front(Value v)
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray.front = v.castInteger;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray.front = v.castDouble;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray.front = v.castString;
            }
        }
        void popBack()
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray.popBack;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray.popBack;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray.popBack;
            }
        }
        Value back()
        {
            if (value.type == ValueType.IntegerArray)
            {
                return Value(integerArray.back);
            }
            if (value.type == ValueType.DoubleArray)
            {
                return Value(doubleArray.back);
            }
            if (value.type == ValueType.StringArray)
            {
                return Value(stringArray.back);
            }
            throw new TypeMismatch();
        }
        void back(Value v)
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray.back = v.castInteger;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray.back = v.castDouble;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray.back = v.castString;
            }
        }
        typeof(this) save()
        {
            return this;
        }
        Value front()
        {
            if (value.type == ValueType.IntegerArray)
            {
                return Value(integerArray.front);
            }
            if (value.type == ValueType.DoubleArray)
            {
                return Value(doubleArray.front);
            }
            if (value.type == ValueType.StringArray)
            {
                return Value(stringArray.front);
            }
            throw new TypeMismatch();
        }
        void opIndexAssign(Value v, size_t index)
        {
            if (value.type == ValueType.IntegerArray)
            {
                integerArray[index] = v.castInteger;
                return;
            }
            if (value.type == ValueType.DoubleArray)
            {
                doubleArray[index] = v.castDouble;
                return;
            }
            if (value.type == ValueType.StringArray)
            {
                stringArray[index] = v.castString;
                return;
            }
            throw new TypeMismatch();
        }
        Value opIndex(size_t index)
        {
            if (value.type == ValueType.IntegerArray)
            {
                return Value(integerArray[index]);
            }
            if (value.type == ValueType.DoubleArray)
            {
                return Value(doubleArray[index]);
            }
            if (value.type == ValueType.StringArray)
            {
                return Value(stringArray[index]);
            }
            throw new TypeMismatch();
        }
        wrappeeer opSlice(size_t x, size_t y)
        {
            wrappeeer aa = wrappeeer(value);
            if (value.type == ValueType.IntegerArray)
            {
                aa.integerArray = integerArray[x..y];
            }
            if (value.type == ValueType.DoubleArray)
            {
                aa.doubleArray = doubleArray[x..y];
            }
            if (value.type == ValueType.StringArray)
            {
                aa.stringArray = stringArray[x..y];
            }
            return aa;
        }
    }
    static string sortGenerator(int count, string less)()
    {
        string buf = "switch(args.length){";
        string args = "";
        for (int i = 0; i < count; i++)
        {
            args ~= ",wrappeeer(args[" ~ (i + 1).to!string ~ "])[start..start + count]";
            buf ~= "case " ~ (i + 2).to!string ~ ":";
            buf ~= "if(args[0].type==ValueType.IntegerArray){sort!(" ~ less ~ ", SwapStrategy.stable)(zip(iarray" ~ args ~ "));}";
            buf ~= "else if(args[0].type==ValueType.DoubleArray){sort!(" ~ less ~ ", SwapStrategy.stable)(zip(darray" ~ args ~ "));}";
            buf ~= "else if(args[0].type==ValueType.StringArray){sort!(" ~ less ~ ", SwapStrategy.stable)(zip(sarray" ~ args ~ "));}";
            buf ~= "break;";
        }
        buf ~= "default:}";
        return buf;
    }
    //もう少しまともな実装できそう
    static void SORT(Value[] arg)
    {
        import std.range;
        int start, count;
        auto args = getSortArgument(arg, start, count);

        int[] iarray;
        double[] darray;
        Array!wchar[] sarray;
        if (args[0].type == ValueType.IntegerArray)
        {
            iarray = args[0].integerArray.array[start..start + count];
        }
        if (args[0].type == ValueType.DoubleArray)
        {
            darray = args[0].doubleArray.array[start..start + count];
        }
        if (args[0].type == ValueType.StringArray)
        {
            sarray = args[0].stringArray.array[start..start + count];
        }
        if (args.length > 1)
        {
            mixin(sortGenerator!(8, "\"a[0]<b[0]\""));
            /*
            if (args[0].type == ValueType.IntegerArray)
            {
                sort!("a[0] < b[0]", SwapStrategy.stable)(zip(args[0].integerArray.array, wrappeeer(args[1])));
            }
            if (args[0].type == ValueType.DoubleArray)
            {
                sort!("a[0] < b[0]", SwapStrategy.stable)(zip(args[0].doubleArray.array, wrappeeer(args[1])));
            }
            if (args[0].type == ValueType.StringArray)
            {
                sort!("a[0] < b[0]", SwapStrategy.stable)(zip(args[0].stringArray.array, wrappeeer(args[1])));
            }*/
        }
        else
        {
            if (args[0].type == ValueType.IntegerArray)
            {
                sort!("a < b", SwapStrategy.stable)(iarray);
            }
            if (args[0].type == ValueType.DoubleArray)
            {
                sort!("a < b", SwapStrategy.stable)(darray);
            }
            if (args[0].type == ValueType.StringArray)
            {
                sort!("a < b", SwapStrategy.stable)(sarray);
            }
        }
    }
    static void RSORT(Value[] arg)
    {
        import std.range;
        int start, count;
        auto args = getSortArgument(arg, start, count);

        int[] iarray;
        double[] darray;
        Array!wchar[] sarray;
        if (args[0].type == ValueType.IntegerArray)
        {
            iarray = args[0].integerArray.array[start..start + count];
        }
        if (args[0].type == ValueType.DoubleArray)
        {
            darray = args[0].doubleArray.array[start..start + count];
        }
        if (args[0].type == ValueType.StringArray)
        {
            sarray = args[0].stringArray.array[start..start + count];
        }
        if (args.length > 1)
        {
            mixin(sortGenerator!(8, "\"a[0]>b[0]\""));
        }
        else
        {
            if (args[0].type == ValueType.IntegerArray)
            {
                sort!("a > b", SwapStrategy.stable)(iarray);
            }
            if (args[0].type == ValueType.DoubleArray)
            {
                sort!("a > b", SwapStrategy.stable)(darray);
            }
            if (args[0].type == ValueType.StringArray)
            {
                sort!("a > b", SwapStrategy.stable)(sarray);
            }
        }
    }
    static double MAX(Value array)
    {
        if (array.type == ValueType.IntegerArray)
        {
            return minPos!"a > b"(array.integerArray.array)[0];
        }
        if (array.type == ValueType.DoubleArray)
        {
            return minPos!"a > b"(array.doubleArray.array)[0];
        }
        throw new TypeMismatch();
    }
    static double MAX(Value[] args)
    {
        if (args.length == 0)
        {
            throw new IllegalFunctionCall("MAX");
        }
        return minPos!"a.castDouble > b.castDouble"(args)[0].castDouble;
    }
    static double MIN(Value array)
    {
        if (array.type == ValueType.IntegerArray)
        {
            return minPos!"a < b"(array.integerArray.array)[0];
        }
        if (array.type == ValueType.DoubleArray)
        {
            return minPos!"a < b"(array.doubleArray.array)[0];
        }
        throw new TypeMismatch();
    }
    static double MIN(Value[] args)
    {
        if (args.length == 0)
        {
            throw new IllegalFunctionCall("MIN");
        }
        return minPos!"a.castDouble < b.castDouble"(args)[0].castDouble;
    }
    //MAX(2,0)*&H7FFFFFFFF!=MAX(2,0,0)*&H7FFFFFFFF
    static Value MAX(Value a1, Value a2)
    {
        if (a1.type == ValueType.Integer && a2.type == ValueType.Integer)
        {
            return Value(a1.integerValue > a2.integerValue ? a1.integerValue : a2.integerValue);
        }
        return Value(a1.castDouble > a2.castDouble ? a1.castDouble : a2.castDouble);
    }
    static Value MIN(Value a1, Value a2)
    {
        if (a1.type == ValueType.Integer && a2.type == ValueType.Integer)
        {
            return Value(a1.integerValue < a2.integerValue ? a1.integerValue : a2.integerValue);
        }
        return Value(a1.castDouble < a2.castDouble ? a1.castDouble : a2.castDouble);
    }
    static pure nothrow @nogc @safe double EXP()
    {
        return std.math.E;
    }
    static pure nothrow @nogc @safe double EXP(double d)
    {
        return std.math.exp(d);
    }
    static double LOG(double a)
    {
        if (a <= 0)
        {
            throw new OutOfRange();
        }
        return std.math.log(a);
    }
    static double LOG(double a, double b)
    {
        if (a <= 0)
        {
            throw new OutOfRange();
        }
        if (b <= 1)
        {
            throw new OutOfRange();
        }
        return std.math.log(a) / std.math.log(b);
    }
    static void PUSH(Value ary, Value exp)
    {
        if (!ary.isArray)
        {
            throw new TypeMismatch("PUSH", 1);
        }
        if (!exp.canCast(ary.elementType))
        {
            throw new TypeMismatch("PUSH");
        }
        switch (ary.type)
        {
            case ValueType.String:
                if (ary.stringValue.dimCount != 1)
                    throw new TypeMismatch("PUSH", 1);
                ary.stringValue.push(exp.castString);
                break;
            case ValueType.IntegerArray:
                if (ary.integerArray.dimCount != 1)
                    throw new TypeMismatch("PUSH", 1);
                ary.integerArray.push(exp.castInteger);
                break;
            case ValueType.DoubleArray:
                if (ary.doubleArray.dimCount != 1)
                    throw new TypeMismatch("PUSH", 1);
                ary.doubleArray.push(exp.castDouble);
                break;
            case ValueType.StringArray:
                if (ary.stringArray.dimCount != 1)
                    throw new TypeMismatch("PUSH", 1);
                ary.stringArray.push(exp.castString);
                break;
            default:
                throw new TypeMismatch();
        }
    }
    static Value POP(Value ary)
    {
        if (!ary.isArray)
        {
            throw new TypeMismatch("POP", 1);
        }
        if (ary.length == 0)
        {
            throw new SubscriptOutOfRange("POP");
        }
        switch (ary.type)
        {
            case ValueType.String:
                if (ary.stringValue.dimCount != 1)
                    throw new TypeMismatch("POP", 1);
                return Value(ary.stringValue.pop());
            case ValueType.IntegerArray:
                if (ary.integerArray.dimCount != 1)
                    throw new TypeMismatch("POP", 1);
                return Value(ary.integerArray.pop());
            case ValueType.DoubleArray:
                if (ary.doubleArray.dimCount != 1)
                    throw new TypeMismatch("POP", 1);
                return Value(ary.doubleArray.pop());
            case ValueType.StringArray:
                if (ary.stringArray.dimCount != 1)
                    throw new TypeMismatch("POP", 1);
                return Value(ary.stringArray.pop());
            default:
                throw new TypeMismatch();
        }
    }
    static void UNSHIFT(Value ary, Value exp)
    {
        if (!ary.isArray)
        {
            throw new TypeMismatch("UNSHIFT", 1);
        }
        if (!exp.canCast(ary.elementType))
        {
            throw new TypeMismatch("UNSHIFT");
        }
        if (ary.dimCount != 1)
            throw new TypeMismatch("UNSHIFT", 1);
        switch (ary.type)
        {
            case ValueType.String:
                ary.stringValue.unshift(exp.castString);
                break;
            case ValueType.IntegerArray:
                ary.integerArray.unshift(exp.castInteger);
                break;
            case ValueType.DoubleArray:
                ary.doubleArray.unshift(exp.castDouble);
                break;
            case ValueType.StringArray:
                ary.stringArray.unshift(exp.castString);
                break;
            default:
                throw new TypeMismatch();
        }
    }
    static Value SHIFT(Value ary)
    {
        if (!ary.isArray)
        {
            throw new TypeMismatch("SHIFT", 1);
        }
        if (ary.dimCount != 1)
            throw new TypeMismatch("SHIFT", 1);
        if (ary.length == 0)
        {
            throw new SubscriptOutOfRange("SHIFT");
        }
        switch (ary.type)
        {
            case ValueType.String:
                return Value(ary.stringValue.shift());
            case ValueType.IntegerArray:
                return Value(ary.integerArray.shift());
            case ValueType.DoubleArray:
                return Value(ary.doubleArray.shift());
            case ValueType.StringArray:
                return Value(ary.stringArray.shift());
            default:
                throw new TypeMismatch();
        }
    }
    static void BACKTRACE(PetitComputer p)
    {
        auto bt = p.vm.backTrace;
        foreach (t; bt)
        {
            p.console.print(t.slot, ":\t", t.line, ":\t", t.name, "\n");
            //TODO:=== Press ENTER ===
        }
    }
    static void FILL(Value array, Value value)
    {
        if (array.isString || !array.isArray)
            throw new TypeMismatch("FILL", 1);
        FILL(array, value, 0, array.length);
    }
    static void FILL(Value array, Value value, int offset)
    {
        if (array.isString || !array.isArray)
            throw new TypeMismatch("FILL", 1);
        FILL(array, value, offset, array.length - offset);
    }
    static void FILL(Value array, Value value, int offset, int len)
    {
        if (array.isString || !array.isArray)
            throw new TypeMismatch("FILL", 1);
        if (!value.canCast(array.elementType))
            throw new TypeMismatch("FILL"/*no argument number*/);
        if (offset < 0)
            throw new OutOfRange("FILL", 3);
        if (array.length < offset + len)
            throw new OutOfRange("FILL", 4);
        if (array.type == ValueType.StringArray)
        {
            for (int i = 0; i < len; i++)
            {
                array.stringArray.array[offset + i] = value.castString.dup/*copy*/;
            }
        }
        if (array.type == ValueType.IntegerArray)
        {
            array.integerArray.array[offset..offset + len] = value.castInteger;
        }
        if (array.type == ValueType.DoubleArray)
        {
            array.doubleArray.array[offset..offset + len] = value.castDouble;
        }
    }
    static void KEY(PetitComputer p, int index, wstring key)
    {
        index--;
        if (index < 0 || index >= p.functionKey.length)
            throw new OutOfRange("KEY", 1);
        p.functionKey[index] = key.dup;
    }
    static void KEY(PetitComputer p, int index, out Array!wchar key)
    {
        index--;
        if (index < 0 || index >= p.functionKey.length)
            throw new OutOfRange("KEY", 1);
        key = new Array!wchar(cast(wchar[])p.functionKey[index]);
    }
    import otya.smilebasic.dialog;
    import otya.smilebasic.project;
    static void DIALOG(PetitComputer p, wstring text, out int result)
    {
        auto dialog = new Dialog(p);
        result = p.project.result = cast(DialogResult)dialog.show(text);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType, out int result)
    {
        selType.setDefaultValue(0);
        auto dialog = new Dialog(p);
        result = p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType, DefaultValue!wstring cap, out int result)
    {
        selType.setDefaultValue(0);
        cap.setDefaultValue("■DIALOG");
        auto dialog = new Dialog(p);
        result = p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType, cast(wstring)cap);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType, DefaultValue!wstring cap, DefaultValue!int timeout, out int result)
    {
        selType.setDefaultValue(0);
        cap.setDefaultValue("■DIALOG");
        timeout.setDefaultValue(0);
        auto dialog = new Dialog(p);
        result = p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType, cast(wstring)cap, cast(int)timeout);
    }
    static void DIALOG(PetitComputer p, wstring text)
    {
        auto dialog = new Dialog(p);
        p.project.result = cast(DialogResult)dialog.show(text);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType)
    {
        selType.setDefaultValue(0);
        auto dialog = new Dialog(p);
        p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType, DefaultValue!wstring cap)
    {
        selType.setDefaultValue(0);
        cap.setDefaultValue("■DIALOG");
        auto dialog = new Dialog(p);
        p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType, cast(wstring)cap);
    }
    static void DIALOG(PetitComputer p, wstring text, DefaultValue!int selType, DefaultValue!wstring cap, DefaultValue!int timeout)
    {
        selType.setDefaultValue(0);
        cap.setDefaultValue("■DIALOG");
        timeout.setDefaultValue(0);
        auto dialog = new Dialog(p);
        p.project.result = cast(DialogResult)dialog.show(text, cast(SelectionType)selType, cast(wstring)cap, cast(int)timeout);
    }
    static void PRGEDIT(PetitComputer p, int slot)
    {
        PRGEDIT(p, slot, 1);
    }
    static void PRGEDIT(PetitComputer p, int slot, int line)
    {
        if (slot < 0 || p.program.slotSize <= slot)
            throw new OutOfRange("PRGEDIT", 1);
        if (line < -1 || line == 0)
            throw new OutOfRange("PRGEDIT"/*, 2*/);
        p.program.edit(slot, line);
    }
    static wstring PRGGET(PetitComputer p)
    {
        return p.program.get();
    }
    static void PRGSET(PetitComputer p, wstring x)
    {
        p.program.set(x.dup);
    }
    static void PRGINS(PetitComputer p, wstring line)
    {
        p.program.insert(line, false);
    }
    static void PRGINS(PetitComputer p, wstring line, int isBack)
    {
        p.program.insert(line, cast(bool)isBack);
    }
    static void PRGDEL(PetitComputer p)
    {
        PRGDEL(p, 1);
    }
    static void PRGDEL(PetitComputer p, int count)
    {
        if (count == 0)
        {
            throw new OutOfRange("PRGDEL", 1);
        }
        p.program.delete_(count);
    }
    static int PRGSIZE(PetitComputer p)
    {
        return PRGSIZE(p, p.vm.currentSlotNumber, 0);
    }
    static int PRGSIZE(PetitComputer p, int slot)
    {
        return PRGSIZE(p, slot, 0);
    }
    static int PRGSIZE(PetitComputer p, int slot, int type)
    {
        import otya.smilebasic.program;
        return p.program.size(slot, cast(SizeType)type);
    }
    static wstring PRGNAME(PetitComputer p)
    {
        return p.program.name(p.vm.currentSlotNumber);
    }
    static wstring PRGNAME(PetitComputer p, int slot)
    {
        return p.program.name(slot);
    }
    static auto split2(R)(R range, size_t size)
    {
        return Split2!R(range, size);
    }
    unittest
    {
        struct IR
        {
            void popFront()
            {
            }
            bool empty()
            {
                return true;
            }
            int front()
            {
                return 1;
            }
        }
        static assert(isInputRange!(Split2!IR));
        static assert(isInputRange!(Split2!(int[])));
    }
    struct Split2(R)
    if (isInputRange!R)
    {
        R range;
        size_t size;
        ElementType!(R)[] item;
        bool isEmpty;
        this(R range, size_t size)
        {
            this.range = range;
            this.size = size;
            popFront();
        }
        void popFront()
        {
            isEmpty = range.empty;
            if (isEmpty)
                return;
            static if (hasSlicing!R && is(range[0..size] == item))
            {
                item = range[0..size];
                popFrontN(item, size);
            }
            else
            {
                item = new ElementType!(R)[size];
                for (size_t i = 0; i < size; i++)
                {
                    item[i] = range.front();
                    range.popFront();
                }
            }
        }
        auto front()
        {
            return item;
        }
        bool empty()
        {
            return isEmpty;
        }
    }
    static void FONTDEF(PetitComputer p, int icode, Value array)
    {
        if (icode < 0 || icode > 0xFFFF)
        {
            throw new OutOfRange("FONTDEF", 1);
        }
        ushort code = cast(ushort)icode;
        if (!p.console.canDefine(code))
        {
            throw new IllegalFunctionCall("FONTDEF"/*, 1*/);
        }
        int color = 4;
        if (array.isString)
        {
            wstring definition = array.castDString;
            //FONTDEF 0,"0000"*65'=>OK
            if (definition.length < p.console.fontDefWidth * p.console.fontDefHeight * color)
            {
                throw new IllegalFunctionCall("FONTDEF", 2);
            }
            int[] font = new int[p.console.fontDefWidth * p.console.fontDefHeight * color];
            try
            {
                p.console.define(code, split2(definition, 4).map!(x => x.to!int(16)).array);
            }
            catch (ConvException)
            {
                throw new IllegalFunctionCall("FONTDEF", 2);
            }
            return;
        }
        //DIM A$[63]
        //FONTDEF 0,A$'=>Subscript out of range(FONTDEF:2)
        //DIM A$[64]
        //FONTDEF 0,A$'=>Type mismatch(FONTDEF:2)
        //FONTDEF 0,1'=>Type mismatch(FONTDEF:2)

        if (!array.isArray)
            throw new TypeMismatch("FONTDEF", 2);
        if (array.length < p.console.fontDefWidth * p.console.fontDefHeight)
            throw new SubscriptOutOfRange("FONTDEF", 2);
        if (!array.isNumberArray)
            throw new TypeMismatch("FONTDEF", 2);
        if (array.type == ValueType.IntegerArray)
        {
            p.console.define(code, array.integerArray.array);
        }
        else if (array.type == ValueType.DoubleArray)
        {
            p.console.define(code, array.doubleArray.array);
        }
    }
    static void FONTDEF(PetitComputer p)
    {
        p.console.initGRPF();
    }
    static void SCROLL(PetitComputer p, int x, int y)
    {
        p.console.scroll(x, y);
    }
    static void CLIPBOARD(PetitComputer p, wstring value)
    {
        p.clipboard = value;
    }
    static wstring CLIPBOARD(PetitComputer p)
    {
        return p.clipboard;
    }

    //alias void function(PetitComputer, Value[], Value[]) BuiltinFunc;
    static BuiltinFunctions[wstring] builtinFunctions;
    static wstring getBasicName(BFD)(const wstring def)
    {
        enum attr = __traits(getAttributes, __traits(getOverloads, BFD.C_, BFD.N)[BFD.I_]);
        wstring r = def;
        foreach(i; attr)
        {
            if(__traits(compiles, i.naame))
            {
                r = (cast(BasicName)i).naame;
            }
        }
        return r;
    }

    static this()
    {
        foreach(name; __traits(derivedMembers, BuiltinFunction))
        {
            //writeln(name);
            static if(/*__traits(isStaticFunction, __traits(getMember, BuiltinFunction, name)) && */name[0].isUpper)
            {
                foreach(i, F; __traits(getOverloads, BuiltinFunction, name))
                {
                    //pragma(msg, AddFunc!(BuiltinFunction, name));
                    wstring suffix = "";
                    if(is(ReturnType!(__traits(getMember, BuiltinFunction, name)) == wstring))
                    {
                        suffix = "$";
                    }
                    alias BFD = BuiltinFunctionData!(BuiltinFunction, name, i);
                    wstring name2 = getBasicName!BFD(name ~ suffix);
                    auto func = builtinFunctions.get(name2, null);
                    //pragma(msg, AddFunc!BFD);
                    auto f = new BuiltinFunction(
                                                 GetFunctionParamType!(BFD),
                                                 GetFunctionReturnType!(BFD),
                                                 mixin(AddFunc!(BFD)),
                                                 GetStartSkip!(BFD),
                                                 IsVariadic!(BFD),
                                                 name,
                                                 GetOutStartSkip!(BFD)
                                                 );
                    if(func)
                    {
                        builtinFunctions[name2].addFunction(f);
                    }
                    else
                    {
                        builtinFunctions[name2] = new BuiltinFunctions(f);
                    }
                    //writeln(AddFunc!(BuiltinFunction, name));
                }
            }
        }
    }

}
template GetOutStartSkip(BFD)
{
    static if(__traits(getAttributes, __traits(getOverloads, BFD.C_, BFD.N)[BFD.I_]).length == 1 &&
              is(typeof(__traits(getAttributes, __traits(getOverloads, BFD.C_, BFD.N)[BFD.I_])[0]) == StartOptional))
    {
        enum so = __traits(getAttributes, __traits(getOverloads, BFD.C_, BFD.N)[BFD.I_])[0];
        int GetOutStartSkip()
        {
            auto result = 0;
            int k;
            foreach (j, i; ParameterIdentifierTuple!(__traits(getOverloads, BFD.C_, BFD.N)[BFD.I_]))
            {
                if(i == so.name)
                {
                    result = k;
                    break;
                }
                else if(BFD.ParameterStorageClass[j] & ParameterStorageClass.out_)
                {
                    k++;
                }
            }
            return result;
        }
    }
    else
    {
        int GetOutStartSkip()
        {
            return 0;
        }
    }
}
template GetStartSkip(BFD)
{
    private template SkipSkip(int I, P...)
    {
        static if(P.length <= I)
        {
            enum SkipSkip = I - is(P[0] == PetitComputer);
        }
        else static if(BFD.ParameterStorageClass[I] & ParameterStorageClass.out_)
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else static if(is(P[I] == DefaultValue!(int, false)))
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else static if(is(P[I] == DefaultValue!(double, false)))
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else static if(is(P[I] == DefaultValue!(wstring, false)))
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else static if(is(P[I] == DefaultValue!(Value, false)))
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else static if(is(P[I] == Value[]))
        {
            enum SkipSkip = I - is(P[0] : PetitComputer);
        }
        else
        {
            enum SkipSkip = SkipSkip!(I + 1, P);
        }
    }
    enum GetStartSkip = SkipSkip!(0, BFD.ParameterType);
}
template GetBuiltinFunctionArgment(P...)
{
    static if(is(P[0] == double))
    {
        const string arg = "ValueType.Double, false";
    }
    else static if(is(P[0] == int))
    {
        const string arg = "ValueType.Integer, false";
    }
    else static if(is(P[0] == wstring))
    {
        const string arg = "ValueType.String, false";
    }
    else static if(is(P[0] == Array!wchar))
    {
        const string arg = "ValueType.String, false";
    }
    else static if(is(P[0] == DefaultValue!int))
    {
        const string arg = "ValueType.Integer, true";
    }
    else static if(is(P[0] == DefaultValue!(int, false)))
    {
        const string arg = "ValueType.Integer, true";
    }
    else static if(is(P[0] == DefaultValue!double))
    {
        const string arg = "ValueType.Double, true";
    }
    else static if(is(P[0] == DefaultValue!(double, false)))
    {
        const string arg = "ValueType.Double, true";
    }
    else static if(is(P[0] == DefaultValue!(wstring)))
    {
        const string arg = "ValueType.String, false";
    }
    else static if(is(P[0] == DefaultValue!(wstring, false)))
    {
        const string arg = "ValueType.String, true";
    }
    else static if(is(P[0] == Value[]))
    {
        const string arg = "";
    }
    else static if(is(P[0] == DefaultValue!(Value)) || is(P[0] == Value))
    {
        const string arg = "ValueType.Void, false";
    }
    else static if(is(P[0] == DefaultValue!(Value, false)))
    {
        const string arg = "ValueType.Void, true";
    }
    else static if(is(P[0] == void))
    {
        const string arg = "";
    }
    else
    {
        static assert(false, "Invalid type");
    }
    static if(is(P[0] == void))
    {
        enum GetBuiltinFunctionArgment = "";
    }
    else
    {
        enum GetBuiltinFunctionArgment = "BuiltinFunctionArgument(" ~ arg ~ ")";
    }
}
template BuiltinFunctionData(C, string NAME, int I)
{
    //struct BuiltinFunctionData
    //{
        //enum P = ParameterStorageClassTuple!(__traits(getOverloads, C, N)[I]);
    struct BuiltinFunctionData
    {
        alias P = std.traits.ParameterStorageClassTuple!(__traits(getOverloads, C, NAME)[I]);
        alias T = std.traits.ParameterTypeTuple!(__traits(getOverloads, C, NAME)[I]);
        alias R = std.traits.ReturnType!(__traits(getOverloads, C, NAME)[I]);
        alias ParameterStorageClass = std.traits.ParameterStorageClassTuple!(__traits(getOverloads, C, NAME)[I]);
        alias ParameterType = std.traits.ParameterTypeTuple!(__traits(getOverloads, C, NAME)[I]);
        alias ReturnType = std.traits.ReturnType!(__traits(getOverloads, C, NAME)[I]);
        enum F = &__traits(getOverloads, C, NAME)[I];
        alias N = NAME;
        alias C_ = C;
        alias I_ = I;
    }
}
template GetOutArgment(C, string N)
{
    alias T = ParameterTypeTuple!(__traits(getMember, C, N));
    string GetOutArgment2()
    {
        string arg = "";
        foreach(i, J; T)
        {
            enum P = ParameterStorageClassTuple!(__traits(getMember, C, N))[i];
            static if(P & ParameterStorageClass.out_)
            {
                arg ~= GetBuiltinFunctionArgment!(J) ~ ",";
            }
        }
        return arg;
    }
    enum GetOutArgment = GetOutArgment2();
}
template GetOutArgment2(BFD)
{
    alias T = BFD.T;
    string GetOutArgment22()
    {
        string arg = "";
        foreach(i, J; T)
        {
            //enum P = ParameterStorageClassTuple!(__traits(getMember, C, N))[i];
            static if(BFD.P[i] & ParameterStorageClass.out_)
            {
                arg ~= GetBuiltinFunctionArgment!(J) ~ ",";
            }
        }
        return arg;
    }
    enum GetOutArgment2 = GetOutArgment22();
}
//template GetOutArgment2(T2)
//{
//}
template GetFunctionReturnType(BFD)
{
    static if(is(BFD.R == void))
    {
        enum GetFunctionReturnType = 
           mixin("[" ~ GetOutArgment2!(BFD) ~ "]");
    }
    else
    {
        enum GetFunctionReturnType = 
           mixin("[" ~ GetBuiltinFunctionArgment!(BFD.R) ~ "]");
    }
}
template AddFunc(BFD)
{
    static if(is(BFD.ReturnType == double) || is(BFD.ReturnType == int))
    {
        const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){if(ret.length != 1){throw new IllegalFunctionCall(\"" ~ BFD.N ~ "\");}ret[0] = Value(" ~ BFD.N ~ "(" ~
            AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(BFD) - 1, 0, 0, 0, BFD,
                         BFD.ParameterType) ~ "));}";
    }
    else static if(is(BFD.ReturnType == void))
    {
        //pragma(msg, GetArgumentCount!(T,N));
        const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){/*if(ret.length != 0){throw new IllegalFunctionCall(\"" ~ BFD.N ~ "\");}*/" ~ OutArgsInit!(BFD) ~ BFD.N ~ "(" ~
            AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(BFD) - 1, 0, 0, 0, BFD,
                        BFD.ParameterType) ~ ");}";
    }
    else static if(is(BFD.ReturnType == wstring))
    {
        const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){if(ret.length != 1){throw new IllegalFunctionCall(\"" ~ BFD.N ~ "\");}ret[0] = Value(" ~ BFD.N ~ "(" ~
            AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(BFD) - 1, 0, 0, 0, BFD,
                        BFD.ParameterType) ~ "));}";
    }
    else static if(is(BFD.ReturnType == Value))
    {
        const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){if(ret.length != 1){throw new IllegalFunctionCall(\"" ~ BFD.N ~ "\");}ret[0] = " ~ BFD.N ~ "(" ~
            AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(BFD) - 1, 0, 0, 0, BFD,
                        BFD.ParameterType) ~ ");}";
    }
    else
    {
        const string AddFunc = "";
        static assert(false, "Invalid type");
    }
}
DefaultValue!int fromIntToDefault(Value v)
{
    if(v.isNumber)
        return DefaultValue!int(v.castInteger());
    else
        return DefaultValue!int(true);
}
DefaultValue!(int, false) fromIntToSkip(Value v)
{
    if(v.isNumber)
        return DefaultValue!(int, false)(v.castInteger());
    else
        return DefaultValue!(int, false)(true);
}
DefaultValue!double fromDoubleToDefault(Value v)
{
    if(v.isNumber)
        return DefaultValue!double(v.castDouble());
    else
        return DefaultValue!double(true);
}
DefaultValue!(double, false) fromDoubleToSkip(Value v)
{
    if(v.isNumber)
        return DefaultValue!(double, false)(v.castDouble());
    else
        return DefaultValue!(double, false)(true);
}
DefaultValue!wstring fromStringToDefault(Value v)
{
    if(v.type == ValueType.String)
        return DefaultValue!wstring(v.castDString());
    else
        return DefaultValue!wstring(true);
}
DefaultValue!(wstring, false) fromStringToSkip(Value v)
{
    if(v.type == ValueType.String)
        return DefaultValue!(wstring, false)(v.castDString());
    else
        return DefaultValue!(wstring, false)(true);
}
DefaultValue!Value fromValueToDefault(Value v)
{
    if(v.type != ValueType.Void)
        return DefaultValue!Value(v);
    else
        return DefaultValue!Value(true);
}
DefaultValue!(Value, false) fromValueToSkip(Value v)
{
    if(v.type != ValueType.Void)
        return DefaultValue!(Value, false)(v);
    else
        return DefaultValue!(Value, false)(true);
}
template GetFunctionParamType(BFD)
{
    enum GetFunctionParamType = mixin("[" ~ Array!(0, BFD.T) ~ "]");
    private template Array(int I, P...)
    {
        static if(P.length == 0)
        {
            const string arg = "";
            enum Array = "";
        }
        else static if(BFD.ParameterStorageClass[I] & ParameterStorageClass.out_)
        {
            static if(1 == P.length && !is(P[0] == PetitComputer))
            {
                enum Array = "";
            }
            else static if(!is(P[0] == PetitComputer))
            {
                enum Array = Array!(I + 1, P[1..$]);
            }
        }
        else
        {
            static if(is(P[0] == double))
            {
                const string arg = "ValueType.Double, false";
            }
            else static if(is(P[0] == int))
            {
                const string arg = "ValueType.Integer, false";
            }
            else static if(is(P[0] == wstring))
            {
                const string arg = "ValueType.String, false";
            }
            else static if(is(P[0] == DefaultValue!int))
            {
                const string arg = "ValueType.Integer, true";
            }
            else static if(is(P[0] == DefaultValue!(int, false)))
            {
                const string arg = "ValueType.Integer, true";
            }
            else static if(is(P[0] == DefaultValue!double))
            {
                const string arg = "ValueType.Double, true";
            }
            else static if(is(P[0] == DefaultValue!(double, false)))
            {
                const string arg = "ValueType.Double, true";
            }
            else static if(is(P[0] == DefaultValue!(wstring)))
            {
                const string arg = "ValueType.String, false";
            }
            else static if(is(P[0] == DefaultValue!(wstring, false)))
            {
                const string arg = "ValueType.String, true";
            }
            else static if(is(P[0] == Value[]))
            {
                const string arg = "";
            }
            else static if(is(P[0] == DefaultValue!(Value)) || is(P[0] == Value))
            {
                const string arg = "ValueType.Void, false";
            }
            else static if(is(P[0] == DefaultValue!(Value, false)))
            {
                const string arg = "ValueType.Void, true";
            }
            else static if(is(P[0] == PetitComputer))
            {
                static if(P.length != 0)
                {
                    enum Array = Array!(I + 1, P[1..$]);
                }
                else
                {
                    enum Array = "";
                }
            }
            static if(1 == P.length && !is(P[0] == PetitComputer))
            {
                enum Array = arg.empty ? "" : "BuiltinFunctionArgument(" ~ arg ~ ")";
            }
            else static if(!is(P[0] == PetitComputer))
            {
                enum Array = (arg.empty ? "" : "BuiltinFunctionArgument(" ~ arg ~ ")," )~ Array!(I + 1, P[1..$]);
            }
        }
    }
}
template AddFuncArg(int L, int N, int M, int O, BFD, P...)
{
    enum I = L - N;
    static if(BFD.ParameterStorageClass.length <= M)
    {
        const string AddFuncArg = "";
    }
    else
    {
        enum storage = BFD.ParameterStorageClass[M];
        static if(is(P[0] == double))
        {
            static if(storage & ParameterStorageClass.out_)
            {
                enum add = 0;
                enum outadd = 1;
                const string arg = "ret[" ~ O.to!string ~ "].doubleValue";
            }
            else
            {
                enum add = 1;
                enum outadd = 0;
                const string arg = "arg[" ~ I.to!string ~ "].castDouble";
            }
        }
        else static if(is(P[0] == PetitComputer))
        {
            enum add = 0;
            enum outadd = 0;
            const string arg = "p";
        }
        else static if(is(P[0] == int))
        {
            static if(storage & ParameterStorageClass.out_)
            {
                enum add = 0;
                enum outadd = 1;
                const string arg = "ret[" ~ O.to!string ~ "].integerValue";
            }
            else
            {
                enum add = 1;
                enum outadd = 0;
                const string arg = "arg[" ~ I.to!string ~ "].castInteger";
            }
        }
        else static if(is(P[0] == wstring))
        {
            static if(storage & ParameterStorageClass.out_)
            {
                static assert(false, "Invalid argument out wstring");
            }
            else
            {
                enum add = 1;
                enum outadd = 0;
                const string arg = "arg[" ~ I.to!string ~ "].castDString";
            }
        }
        else static if(is(P[0] == Array!wchar))
        {
            static if(storage & ParameterStorageClass.out_)
            {
                enum add = 0;
                enum outadd = 1;
                const string arg = "ret[" ~ O.to!string ~ "].stringValue";
            }
            else
            {
                enum add = 1;
                enum outadd = 0;
                const string arg = "arg[" ~ I.to!string ~ "].castString";
            }
        }
        else static if(is(P[0] == DefaultValue!int))
        {
            static if(storage & ParameterStorageClass.out_)
            {
                enum add = 0;
                enum outadd = 1;
                const string arg = "ret[" ~ O.to!string ~ "].integerValue";
            }
            else
            {
                enum add = 1;
                enum outadd = 0;
                const string arg = "fromIntToDefault(arg[" ~ I.to!string ~ "])";
            }
        }
        else static if(is(P[0] == DefaultValue!(int, false)))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromIntToSkip(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == DefaultValue!double))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromDoubleToDefault(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == DefaultValue!(double, false)))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromDoubleToSkip(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == DefaultValue!wstring))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromStringToDefault(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == DefaultValue!(wstring, false)))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromStringToSkip(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == Value[]))
        {
            const string arg = "arg";
        }
        else static if(is(P[0] == Value))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "arg[" ~ I.to!string ~ "]";
        }
        else static if(is(P[0] == DefaultValue!Value))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromValueToDefault(arg[" ~ I.to!string ~ "])";
        }
        else static if(is(P[0] == DefaultValue!(Value, false)))
        {
            enum add = 1;
            enum outadd = 0;
            const string arg = "fromValueToSkip(arg[" ~ I.to!string ~ "])";
        }
        else
        {
            enum add = 1;
            enum outadd = 0;
            pragma(msg, P[0]);
            static assert(false, "Invalid type");
            const string arg = "";
        }
        static if(1 == P.length)
        {
            const string AddFuncArg = arg;
        }
        else
        {
            const string AddFuncArg = arg ~ ", " ~ AddFuncArg!(L - !add, N + add, M + 1, O + outadd, BFD, P[1..$]);
        }
    }
}
template OutArgsInit(BFD, int I = 0, int J = 0)
{
    alias param = BFD.ParameterType;
    static if(!param.length)
    {
        enum OutArgsInit = "";
    }
    else
    {
        enum tuple = BFD.ParameterStorageClass[I];
        static if(tuple & ParameterStorageClass.out_)
        {
            enum add = 1;
            enum ret1 = "ret[" ~ J.to!string ~ "].type = ";
            static if(is(param[I] == int))
            {
                enum ret2 = "ValueType.Integer;";
            }
            else static if(is(param[I] == DefaultValue!int))
            {
                enum ret2 = "ValueType.Integer;";
            }
            //else static if(is(param[I] == OptionalOutValue!int))
            //{
            //    enum ret2 = "ValueType.Integer;";
            //}
            else static if(is(param[I] == double))
            {
                enum ret2 = "ValueType.Double;";
            }
            else static if(is(param[I] == Value[]))
            {
                enum ret2 = "ValueType.Void;";
            }
            else static if(is(param[I] == wstring))
            {
                enum ret2 = "ValueType.String;";
            }
            else static if(is(param[I] == Array!wchar))
            {
                enum ret2 = "ValueType.String;";
            }
            else
            {
                static assert(false, "invalid type " ~ param[I].stringof); 
            }
            enum result = ret1 ~ ret2;
        }
        else
        {
            enum add = 0;
            enum result = "";
        }
        static if(param.length > I + 1)
        {
            enum OutArgsInit = result ~ OutArgsInit!(BFD, I + 1, J + add);
        }
        else
        {
            enum OutArgsInit = result;
        }
    }
}
template GetArgumentCount(BFD, int I = 0)
{
    alias param = BFD.ParameterType;
    static if(param.length <= I)
    {
        enum GetArgumentCount = 0;
    }
    else
    {
        enum tuple = BFD.ParameterStorageClass[I];
        static if(is(param[I] == PetitComputer))
        {
            enum add = 0 + 1;
        }
        else
        {
            static if(tuple & ParameterStorageClass.out_)
            {
                enum add = 0;
            }
            else
            {
                enum add = 1;
            }
        }
        static if(param.length > I + 1)
        {
            enum GetArgumentCount = add + GetArgumentCount!(BFD, I + 1);
        }
        else
        {
            enum GetArgumentCount = add;
        }
    }
}
template IsVariadic(BFD, int I = 0)
{
    alias param = BFD.ParameterType;
    static if(param.length == 0 || param.length <= I)
    {
        enum IsVariadic = false;
    }
    else static if(is(param[I] == Value[]))
    {
        enum IsVariadic = true;
    }
    else
    {
        enum IsVariadic = IsVariadic!(BFD, I + 1);
    }
}

