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 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; } alias ValueType = otya.smilebasic.type.ValueType; struct BuiltinFunctionArgument { ValueType argType; bool optionalArg; bool skipArg; } class BuiltinFunction { BuiltinFunctionArgument[] argments; ValueType result; void function(PetitComputer, Value[], Value[]) func; int startskip; bool variadic; string name; this(BuiltinFunctionArgument[] argments, ValueType result, void function(PetitComputer, Value[], Value[]) func, int startskip, bool variadic, string name) { this.argments = argments; this.result = result; this.func = func; this.startskip = startskip; this.variadic = variadic; this.name = name; } 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 double SIN(double arg1) { return sin(arg1); } static double SINH(double arg1) { return sinh(arg1); } static double COS(double arg1) { return cos(arg1); } static double COSH(double arg1) { return cosh(arg1); } static double TAN(double arg1) { return tan(arg1); } static double TANH(double arg1) { return tanh(arg1); } static double RAD(double arg1) { return arg1 * std.math.PI / 180; } static double DEG(double arg1) { return arg1 * 180 / std.math.PI; } static 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, DefaultValue!(int, false) z) { x.setDefaultValue(p.CSRX); y.setDefaultValue(p.CSRY); z.setDefaultValue(p.CSRZ); p.CSRX = cast(int)x; p.CSRY = cast(int)y; p.CSRZ = cast(int)z; } static void COLOR(PetitComputer p, DefaultValue!int fore, DefaultValue!(int, false) back) { fore.setDefaultValue(p.consoleForeColor); back.setDefaultValue(p.consoleBackColor); p.consoleForeColor = cast(int)fore; p.consoleBackColor = cast(int)back; } static void VSYNC(PetitComputer p, DefaultValue!int time) { time.setDefaultValue(1); p.vsync(cast(int)time); } static void WAIT(PetitComputer p, DefaultValue!int time) { time.setDefaultValue(1); p.vsync(cast(int)time); } //TODO:プチコンのCLSには引数の個数制限がない static void CLS(PetitComputer p/*vaarg*/) { p.cls; } static void ASSERT__(PetitComputer p, int cond, wstring message) { if(!cond) { p.printConsole("Assertion failed: ", message, "\n"); } assert(cond, message.to!string); } static int BUTTON(PetitComputer p, DefaultValue!(int, false) mode) { return p.button; } static void VISIBLE(PetitComputer p, DefaultValue!(int) console, DefaultValue!(int) graphic, DefaultValue!(int) BG, DefaultValue!(int) sprite) { } static void XSCREEN(PetitComputer p, int mode, DefaultValue!(int, false) a, DefaultValue!(int, false) b) { a.setDefaultValue(512); b.setDefaultValue(512); p.xscreen(mode, cast(int)a, cast(int)b); } static void DISPLAY(PetitComputer p, int display) { p.display(display); } static void GCLS(PetitComputer p, DefaultValue!(int, false) color) { color.setDefaultValue(0); p.gfill(p.useGRP, 0, 0, 511, 511, cast(int)color); } static void GPSET(PetitComputer p, int x, int y, DefaultValue!(int, false) color) { color.setDefaultValue(p.gcolor); p.gpset(p.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.gcolor); p.gline(p.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.gcolor); p.gbox(p.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.gcolor); p.gfill(p.useGRP, x, y, x2, y2, cast(int)color); } static void GCOLOR(PetitComputer p, int color) { p.gcolor = color; } static void GPRIO(PetitComputer p, int z) { } static void GPAGE(PetitComputer p, int showPage, int usePage) { p.showGRP = showPage; p.useGRP = usePage; } static void BGMPLAY(PetitComputer p, int music) { } 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 int x, out int y) { //JOYSTICK? x = 0; y = 0; } static 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 int RND(int max) { import std.random; return uniform(0, max - 1 + 1); } static void DTREAD(DefaultValue!(wstring, false) date, out int Y, out int M, out int D/*W*/) { import std.datetime; auto currentTime = Clock.currTime(); if(date.isDefault) { Y = currentTime.year; M = currentTime.month; D = currentTime.day; } else { import std.format; auto v = date.value; formattedRead(v, "%d/%d/%d", &Y, &M, &D); } } //hairetuha? static int LEN(wstring str) { return str.length; } static double VAL(wstring str) { try { if(str.length > 2 && str[0..2] == "&H") { return str[2..$].to!int(16); } if(str.length > 2 && str[0..2] == "&B") { return str[2..$].to!int(2); } double val = str.to!double; return val; } catch(Exception e) { return 0;//toriaezu } } static double FLOOR(double val) { return val.floor; } static double ROUND(double val) { return val.round; } static double CEIL(double val) { return val.ceil; } static wstring MID(wstring str, int i, int len) { if(i + len > str.length) { return "";//範囲外で空文字 } //挙動未定 return str[i..i + len]; } //INSTRSUSBTLEFT static wstring LEFT(wstring str, int len) { return str[0..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.castString; } else { replace = alen.castString; //省略されたら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) { import std.string; int start = 0; wstring str1, str2; if(!vstr2.isDefault) { start = vstart.castInteger; str1 = vstr1.castString; str2 = cast(wstring)vstr2; } else { str1 = vstart.castString; str2 = vstr1.castString; } int aaa = str1[start..$].indexOf(str2, CaseSensitive.no); return str1[start..$].indexOf(str2, CaseSensitive.no); } static int ASC(wstring str) { return cast(int)str[0]; } static wstring STR(int val) { return val.to!wstring; } static wstring HEX(int val) { //ひどい return val.to!string(16).to!wstring; } static void SPSET(PetitComputer p, int id, int defno) { p.sprite.spset(id, defno); } static void SPHIDE(PetitComputer p, int id) { p.sprite.sphide(id); } static void SPSHOW(PetitComputer p, int id) { p.sprite.spshow(id); } static void SPOFS(PetitComputer p, int id, int x, int y, DefaultValue!(int, false) z) { p.sprite.spofs(id, x, y); } static void SPANIM(PetitComputer p, Value[] va_args) { //TODO:配列 auto args = retro(va_args); int no = args[0].castInteger; double[] animdata = new double[args.length - 2]; int i; foreach(a; args[2..$]) { animdata[i++] = a.castDouble; } if(args[1].isString) p.sprite.spanim(no, args[1].castString, animdata); if(args[1].isNumber) p.sprite.spanim(no, cast(SpriteAnimTarget)(args[1].castInteger), animdata); } static void SPDEF(PetitComputer p, Value[] va_args) { switch(va_args.length) { 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].castString); 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"); return; } default: } { int defno = va_args[0].castInteger; 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 > 3) { W = va_args[3].castInteger; } if(va_args.length > 4) { H = va_args[4].castInteger; } if(va_args.length > 5) { HX = va_args[5].castInteger; } if(va_args.length > 6) { HY = va_args[6].castInteger; } if(va_args.length > 7) { ATTR = va_args[7].castInteger; } p.sprite.SPDEFTable[defno] = SpriteDef(U, V, W, H, HX, HY, cast(SpriteAttr)ATTR); } } static void SPCLR(PetitComputer p, DefaultValue!(int, false) i) { if(i.isDefault) p.sprite.spclr(); else p.sprite.spclr(cast(int)i); } static void SPHOME(PetitComputer p, int i, int hx, int hy) { p.sprite.sphome(i, hx, hy); } static void SPSCALE(PetitComputer p, int i, int x, int y) { p.sprite.spscale(i, x, y); } 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[y][x].character); } static wstring FORMAT(PetitComputer p, Value[] va_args) { alias retro!(Value[]) VaArgs; auto args = retro(va_args); auto format = args[0].castString; import std.array : appender; import std.format; import std.string; auto w = appender!wstring(); int j = 1; for(int i = 0; i < format.length; i++) { auto f = format[i]; if(f == '%') { int d = indexOf(format, 'D', CaseSensitive.yes); if(d != -1) { auto spec = singleSpec(format[i .. d + 1]); spec.spec = 'd'; formatValue(w, args[j].castInteger, spec); j++; i = d; continue; } d = indexOf(format, 'X', CaseSensitive.yes); if(d != -1) { auto spec = singleSpec(format[i .. d + 1]); spec.spec = 'x'; formatValue(w, args[j].castDouble, spec); j++; i = d; continue; } d = indexOf(format, 'S', CaseSensitive.yes); if(d != -1) { auto spec = singleSpec(format[i .. d + 1]); spec.spec = 's'; formatValue(w, args[j].castString, spec); j++; i = d; continue; } d = indexOf(format, 'F', CaseSensitive.yes); if(d != -1) { auto spec = singleSpec(format[i .. d + 1]); spec.spec = 'f'; formatValue(w, args[j].castDouble, spec); j++; i = d; continue; } } w ~= f; } //プチコン互換FOMA return cast(immutable)w.data(); } static wstring CHR(int code) { return (cast(wchar)code).to!wstring; } static double POW(double a1, double a2) { return a1 ^^ a2; } static double SQR(double a1) { return sqrt(a1); } //GalateaTalk利用面倒くさい... static void TALK(wstring a1) { } //alias void function(PetitComputer, Value[], Value[]) BuiltinFunc; static BuiltinFunction[wstring] builtinFunctions; static this() { foreach(name; __traits(derivedMembers, BuiltinFunction)) { writeln(name); static if(/*__traits(isStaticFunction, __traits(getMember, BuiltinFunction, name)) && */name[0].isUpper) { pragma(msg, AddFunc!(BuiltinFunction, name)); wstring suffix = ""; if(is(ReturnType!(__traits(getMember, BuiltinFunction, name)) == wstring)) { suffix = "$"; } builtinFunctions[name ~ suffix] = new BuiltinFunction( GetFunctionParamType!(BuiltinFunction, name), GetFunctionReturnType!(BuiltinFunction, name), mixin(AddFunc!(BuiltinFunction, name)), GetStartSkip!(BuiltinFunction, name), IsVariadic!(BuiltinFunction, name), name, ); writeln(AddFunc!(BuiltinFunction, name)); } } } } template GetStartSkip(T, string N) { private template SkipSkip(int I, P...) { static if(P.length <= I) { 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!(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 { enum SkipSkip = SkipSkip!(I + 1, P); } } enum GetStartSkip = SkipSkip!(0, ParameterTypeTuple!(__traits(getMember, T, N))); } template GetFunctionReturnType(T, string N) { static if(is(ReturnType!(__traits(getMember, T, N)) == double)) { enum GetFunctionReturnType = ValueType.Double; } else static if(is(ReturnType!(__traits(getMember, T, N)) == int)) { enum GetFunctionReturnType = ValueType.Integer; } else static if(is(ReturnType!(__traits(getMember, T, N)) == void)) { enum GetFunctionReturnType = ValueType.Void; } else static if(is(ReturnType!(__traits(getMember, T, N)) == wstring)) { enum GetFunctionReturnType = ValueType.String; } else { enum GetFunctionReturnType = ValueType.Void; static assert(false, "Invalid type"); } } template AddFunc(T, string N) { static if(is(ReturnType!(__traits(getMember, T, N)) == double) || is(ReturnType!(__traits(getMember, T, N)) == int)) { const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){if(ret.length != 1){throw new IllegalFunctionCall(\"" ~ N ~ "\");}ret[0] = Value(" ~ N ~ "(" ~ AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(T,N) - 1, 0, 0, 0, T, N , ParameterTypeTuple!(__traits(getMember, T, N))) ~ "));}"; } else static if(is(ReturnType!(__traits(getMember, T, N)) == void)) { pragma(msg, GetArgumentCount!(T,N)); const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){/*if(ret.length != 0){throw new IllegalFunctionCall(\"" ~ N ~ "\");}*/" ~ OutArgsInit!(T,N) ~ N ~ "(" ~ AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(T,N) - 1, 0, 0, 0, T, N, ParameterTypeTuple!(__traits(getMember, T, N))) ~ ");}"; } else static if(is(ReturnType!(__traits(getMember, T, N)) == wstring)) { const string AddFunc = "function void(PetitComputer p, Value[] arg, Value[] ret){if(ret.length != 1){throw new IllegalFunctionCall(\"" ~ N ~ "\");}ret[0] = Value(" ~ N ~ "(" ~ AddFuncArg!(/*ParameterTypeTuple!(__traits(getMember, T, N)).length*/GetArgumentCount!(T,N) - 1, 0, 0, 0, T, N , ParameterTypeTuple!(__traits(getMember, T, N))) ~ "));}"; } 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!wstring fromStringToDefault(Value v) { if(v.type == ValueType.String) return DefaultValue!wstring(v.castString()); else return DefaultValue!wstring(true); } DefaultValue!(wstring, false) fromStringToSkip(Value v) { if(v.type == ValueType.String) return DefaultValue!(wstring, false)(v.castString()); 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(T, string N) { enum GetFunctionParamType = mixin("[" ~ Array!(ParameterTypeTuple!(__traits(getMember, T, N))) ~ "]"); private template Array(P...) { static if(P.length == 0) { const string arg = ""; enum Array = ""; } 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!(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!(P[1..$]); } else { enum Array = ""; } } static if(1 == P.length && !is(P[0] == PetitComputer)) { enum Array = "BuiltinFunctionArgument(" ~ arg ~ ")"; } else static if(!is(P[0] == PetitComputer)) { enum Array = "BuiltinFunctionArgument(" ~ arg ~ ")," ~ Array!(P[1..$]); } } } } template AddFuncArg(int L, int N, int M, int O, T, string NAME, P...) { enum I = L - N; static if(ParameterStorageClassTuple!(__traits(getMember, T, NAME)).length <= M) { const string AddFuncArg = ""; } else { enum storage = ParameterStorageClassTuple!(__traits(getMember, T, NAME))[M]; static if(is(P[0] == double)) { 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)) { enum add = 1; enum outadd = 0; const string arg = "arg[" ~ I.to!string ~ "].castString"; } else static if(is(P[0] == DefaultValue!int)) { 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!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, T, NAME, P[1..$]); } } } template OutArgsInit(T, string N, int I = 0, int J = 0) { enum tuple = ParameterStorageClassTuple!(__traits(getMember, T, N))[I]; alias param = ParameterTypeTuple!(__traits(getMember, T, N)); static if(!param.length) { enum OutArgsInit = ""; } else { 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;"; } enum result = ret1 ~ ret2; } else { enum add = 0; enum result = ""; } static if(param.length > I + 1) { enum OutArgsInit = result ~ OutArgsInit!(T, N, I + 1, J + add); } else { enum OutArgsInit = result; } } } template GetArgumentCount(T, string N, int I = 0) { alias param = ParameterTypeTuple!(__traits(getMember, T, N)); static if(param.length <= I) { enum GetArgumentCount = 0; } else { enum tuple = ParameterStorageClassTuple!(__traits(getMember, T, N))[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!(T, N, I + 1); } else { enum GetArgumentCount = add; } } } template IsVariadic(T, string N, int I = 0) { alias param = ParameterTypeTuple!(__traits(getMember, T, N)); 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!(T, N, I + 1); } }