diff --git a/ChangeLog b/ChangeLog index 87d5fe23..d1de9100 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,52 @@ +2022-02-23 (12.24) + COMMON: m3Apply no longer crashes if the second argument is not an array + +2022-02-23 (12.24) + EMCC: Implemented emscripten web version + +2022-02-23 (12.24) + COMMON: Fix array access with embedded strings #136 + +2022-02-23 (12.24) + CONSOLE: Added -i command switch for live mode + + This will run the BASIC program in a loop. It can be used with the debug module + to rerun whenever the source code changes: + + import debug + run("xdotool windowactivate `xdotool search --onlyvisible --name \"Emacs\"`") + while (!debug.IsSourceModified()) + rem main loop + wend + +2022-02-23 (12.24) + COMMON: ARRAY now parses json with true/false correctly + +2022-02-19 (12.24) + WEB: Added simple REST server support + +2022-02-19 (12.24) + COMMON: Fixed http_read to handle large HTTP headers + COMMON: Fixed hashmap access issue + +2022-01-16 (12.24) + COMMON: Implemented window.setLocation(x,y) #102 + +2022-01-15 (12.24) + COMMON: Removed 'Meaningless' CDBL, CINT, CREAL. These can be poly-filled with 'DEF' if required. + +2022-01-14 (12.24) + COMMON: Fix #131 - Scalar * Vector doesnt work + +2021-12-31 (12.24) + UI: add support for image.save("file.png") as per console + +2021-12-30 (12.24) + COMMON: parse JSON with SB ";" dimension syntax + +2021-08-22 (12.24) + COMMON: Update plugin system to allow loading to be driven by the IMPORT statement + 2021-08-22 (12.23) SDL: Validate window dimensions on loading to prevent hidden window diff --git a/configure.ac b/configure.ac index a98f6580..c6f51ab8 100644 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ dnl This program is distributed under the terms of the GPL v2.0 dnl Download the GNU Public License (GPL) from www.gnu.org dnl -AC_INIT([smallbasic], [12.23]) +AC_INIT([smallbasic], [12.24]) AC_CONFIG_SRCDIR([configure.ac]) AC_CANONICAL_TARGET @@ -24,6 +24,11 @@ PKG_PROG_PKG_CONFIG TARGET="" dnl define build arguments +AC_ARG_ENABLE(emcc, + AS_HELP_STRING([--enable-emcc],[build emscripten version(default=no)]), + [ac_build_emcc="yes"], + [ac_build_emcc="no"]) + AC_ARG_ENABLE(sdl, AS_HELP_STRING([--enable-sdl],[build SDL version(default=no)]), [ac_build_sdl="yes"], @@ -118,6 +123,10 @@ function checkPCRE() { have_pcre="no" fi + if test x$ac_build_emcc = xyes; then + have_pcre="no" + fi + if test "${have_pcre}" = "yes" ; then AC_DEFINE(USE_PCRE, 1, [match.c used with libpcre.]) PACKAGE_LIBS="${PACKAGE_LIBS} `pcre-config --libs`" @@ -321,6 +330,20 @@ function buildWeb() { AC_SUBST(BUILD_SUBDIRS) } +function buildEmscripten() { + TARGET="Building Emscripten version." + BUILD_SUBDIRS="src/common src/platform/emcc" + AC_CHECK_HEADERS([emscripten.h], [], [AC_MSG_ERROR([emscripten is not installed])]) + AM_CONDITIONAL(WITH_CYGWIN_CONSOLE, false) + AC_DEFINE(_UnixOS, 1, [Building under Unix like systems.]) + AC_DEFINE(_EMCC, 1, [Defined when building emscripten version]) + AC_DEFINE(USE_TERM_IO, 0, [dont use the termios library.]) + AC_DEFINE(IMPL_LOG_WRITE, 1, [Driver implements lwrite()]) + AC_DEFINE(IMPL_DEV_READ, 1, [Implement dev_read()]) + AC_SUBST(BUILD_SUBDIRS) + (cd src/platform/android/app/src/main/assets && xxd -i main.bas > ../../../../../../../src/platform/emcc/main_bas.h) +} + function buildFLTK() { TARGET="Building FLTK version." @@ -389,6 +412,8 @@ elif test x$ac_build_web = xyes; then buildWeb elif test x$ac_build_fltk = xyes; then buildFLTK +elif test x$ac_build_emcc = xyes; then + buildEmscripten else buildConsole fi @@ -425,6 +450,7 @@ Makefile src/common/Makefile src/platform/android/Makefile src/platform/console/Makefile +src/platform/emcc/Makefile src/platform/sdl/Makefile src/platform/web/Makefile src/platform/fltk/Makefile diff --git a/documentation/sbasic_ref.csv b/documentation/sbasic_ref.csv index 1ada9c5d..1e22a7f1 100644 --- a/documentation/sbasic_ref.csv +++ b/documentation/sbasic_ref.csv @@ -26,9 +26,6 @@ Data,command,SEARCH,548,"SEARCH A, key, BYREF ridx [USE cmpfunc]","Scans an arra Data,command,SORT,549,"SORT array [USE cmpfunc]","Sorts an array. The cmpfunc if specified, takes 2 vars to compare and must return: -1 if x < y, +1 if x > y, 0 if x = y." Data,command,SWAP,550,"SWAP a, b","Exchanges the values of two variables. The parameters may be variables of any type." Data,function,ARRAY,1432,"ARRAY [var | expr]","Creates a ARRAY or MAP variable from the given string or expression" -Data,function,CDBL,552,"CDBL (x)","Convert x to 64b real number. Meaningless. Used for compatibility." -Data,function,CINT,553,"CINT (x)","Converts x to 32b integer. Meaningless. Used for compatibility." -Data,function,CREAL,554,"CREAL (x)","Convert x to 64b real number. Meaningless. Used for compatibility." Data,function,ISARRAY,555,"ISARRAY (x)","Returns true if x is an array." Data,function,ISDIR,556,"ISDIR (x)","Returns true if x is a directory." Data,function,ISFILE,557,"ISFILE (x)","Returns true if x is a regular file." diff --git a/samples/distro-examples/tests/all.bas b/samples/distro-examples/tests/all.bas index 1fa63c8f..6f85f909 100644 --- a/samples/distro-examples/tests/all.bas +++ b/samples/distro-examples/tests/all.bas @@ -123,16 +123,13 @@ print "BGETC:" '+ BGETC (fileN) print "BIN:" + BIN (x) print "CAT:" + CAT (x) print "CBS:" + CBS (s) -print "CDBL:" + CDBL (x) print "CEIL:" + CEIL (x) print "CHOP:" + CHOP ("123.45$") print "CHR:" + CHR (87) -print "CINT:" + CINT (x) print "COS:" + COS (x) print "COSH:" + COSH (x) print "COT:" + COT (x) print "COTH:" + COTH (x) -print "CREAL:" + CREAL (x) print "CSC:" + CSC (x) print "CSCH:" + CSCH (x) print "DATE:"' + DATE diff --git a/samples/distro-examples/tests/array.bas b/samples/distro-examples/tests/array.bas index 179fa13a..92da8153 100644 --- a/samples/distro-examples/tests/array.bas +++ b/samples/distro-examples/tests/array.bas @@ -336,3 +336,94 @@ if (a[1] != [3,4]) then throw "err2" if (a[2] != [5,6]) then throw "err3" if (a[3] != [7,8,9]) then throw "err4" +# parse with jsmn and ";" dimension syntax +const font = {"a" : [1,2,3;4,5,6;7,8,999]} +if (ubound(font["a"],1) != 2) then throw "err1" +if (ubound(font["a"],2) != 2) then throw "err2" +a = font["a"] +if (a[2,2] != 999) then throw "err3" + +' +' test for compile errors (incorrect handling of quotes in comp_array_params) +' +dim p(256) +p(asc("=")) = 1 +p(asc("[")) = 1 +p(asc("1")) = 1 +p(asc("2")) = 1 +p(asc("3")) = 1 +p(asc("4")) = 1 +p(asc("5")) = 1 +p(asc("6")) = 1 +p(asc("7")) = 1 +p(asc("8")) = 1 +p(asc("9")) = 1 +p(asc(":")) = 1 +p(asc(";")) = 1 +p(asc("<")) = 1 +p(asc(">")) = 1 +p(asc("?")) = 1 +p(asc("@")) = 1 +p(asc("A")) = 1 +p(asc("B")) = 1 +p(asc("C")) = 1 +p(asc("D")) = 1 +p(asc("E")) = 1 +p(asc("F")) = 1 +p(asc("G")) = 1 +p(asc("H")) = 1 +p(asc("I")) = 1 +p(asc("J")) = 1 +p(asc("K")) = 1 +p(asc("L")) = 1 +p(asc("M")) = 1 +p(asc("N")) = 1 +p(asc("O")) = 1 +p(asc("P")) = 1 +p(asc("Q")) = 1 +p(asc("R")) = 1 +p(asc("S")) = 1 +p(asc("T")) = 1 +p(asc("U")) = 1 +p(asc("V")) = 1 +p(asc("W")) = 1 +p(asc("X")) = 1 +p(asc("Y")) = 1 +p(asc("Z")) = 1 +p(asc("\\"))= 1 +p(asc("]")) = 1 +p(asc("^")) = 1 +p(asc("_")) = 1 +p(asc("`")) = 1 +p(asc("a")) = 1 +p(asc("b")) = 1 +p(asc("c")) = 1 +p(asc("d")) = 1 +p(asc("e")) = 1 +p(asc("f")) = 1 +p(asc("g")) = 1 +p(asc("h")) = 1 +p(asc("i")) = 1 +p(asc("j")) = 1 +p(asc("k")) = 1 +p(asc("l")) = 1 +p(asc("m")) = 1 +p(asc("n")) = 1 +p(asc("o")) = 1 +p(asc("p")) = 1 +p(asc("q")) = 1 +p(asc("r")) = 1 +p(asc("s")) = 1 +p(asc("t")) = 1 +p(asc("u")) = 1 +p(asc("v")) = 1 +p(asc("w")) = 1 +p(asc("x")) = 1 +p(asc("y")) = 1 +p(asc("z")) = 1 +p(asc("{")) = 1 +p(asc("|")) = 1 +p(asc("}")) = 1 +p(asc("~")) = 1 + + diff --git a/samples/distro-examples/tests/hash.bas b/samples/distro-examples/tests/hash.bas index 72f6c47f..97f87e70 100644 --- a/samples/distro-examples/tests/hash.bas +++ b/samples/distro-examples/tests/hash.bas @@ -61,3 +61,22 @@ f.inputs[6].value= "123" if (not isarray(f.inputs)) then throw "post: inputs not a map" endif + +' +' multi-layer access +' +dim lights(1) +shadow.vertices = [] +shadow.vertices << 1 +lights[0].shadows = [] +lights[0].shadows << shadow +lights[0].shadows[0].vertices[0] = [1,2,3,4] + +' +' parse "true" and "false" as boolean fields +' +a= array("{\"stringT\", \"true\", \"stringF\", \"false\", \"booleanT\": true, \"booleanF\": false}") +if (a.stringT <> "true") then throw "not true" +if (a.stringF <> "false") then throw "not false" +if (a.booleanT <> 1) then throw "not true" +if (a.booleanF <> 0) then throw "not false" diff --git a/samples/distro-examples/tests/matrices.bas b/samples/distro-examples/tests/matrices.bas index e0bcb904..75956b91 100644 --- a/samples/distro-examples/tests/matrices.bas +++ b/samples/distro-examples/tests/matrices.bas @@ -39,8 +39,28 @@ C=LinEqn(a,b) print "[x; y; z] = "; C ? -rem -- handling for dot product and multiplication of two 1D arrays +rem - handling for dot product and multiplication of two 1D arrays a1=[1,2,4] a2=[1,4,5] if (a1 * a2 != [1,8,20]) then throw "err" if (a1 % a2 != 29) then throw "err" + +rem - Scalar * Vector doesnt work #131 (https://github.com/smallbasic/SmallBASIC/issues/131) +dim r(2) +v = [5, 10, 10] +s = 2 +r = s * v +if (r[0] != 10) then throw "err1" +if (r[1] != 20) then throw "err2" +if (r[2] != 20) then throw "err3" + +rem - blib_graph.c cleanup m3xx +strip = [[1,0.6], [1,0.2]] +dim m(0 to 2, 0 to 2) +m3ident m +m3trans m, 1,1 +m3scale m, 0, 0, 20, 20 +m3rotate m, 2 +m3Apply m, strip +if (strip != [[-18.23450585285103,14.19218649794793],[-10.96012643824558,17.52136119032507]]) then throw "m3Apply failed" + diff --git a/samples/distro-examples/tests/output/all.out b/samples/distro-examples/tests/output/all.out index f969ccc2..c60404df 100644 --- a/samples/distro-examples/tests/output/all.out +++ b/samples/distro-examples/tests/output/all.out @@ -106,16 +106,13 @@ BGETC: BIN:00000000000000000000000000001100 CAT: CBS:catsanddogs -CDBL:12.3 CEIL:13 CHOP:123.45 CHR:W -CINT:12 COS:0.96473261788661 COSH:109847.99433834482625 COT:-3.66495480230944 COTH:1.00000000004144 -CREAL:12.3 CSC:-3.79893323223389 CSCH:0.00000910348893 DATE: @@ -171,7 +168,7 @@ POINT:0 POLYAREA:0 POLYCENT: POW:12.3 -PROGLINE:191 +PROGLINE:188 PTDISTLN:0 PTDISTSEG:0 PTSIGN:0 diff --git a/src/common/Makefile.am b/src/common/Makefile.am index eb3b7670..41864c78 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -19,7 +19,12 @@ EXTRA_DIST = \ ../include/module.h \ ../include/osd.h \ ../include/var.h \ - ../include/var_map.h + ../include/var_map.h \ + ../lib/jsmn/jsmn.h \ + ../lib/lodepng/lodepng.cpp \ + ../lib/lodepng/lodepng.h \ + ../lib/miniaudio/miniaudio.h \ + ../lib/stb/stb_textedit.h noinst_LIBRARIES = libsb_common.a @@ -42,7 +47,7 @@ libsb_common_a_SOURCES = \ system.c \ random.c \ eval.c \ - extlib.c extlib.h \ + plugins.c plugins.h \ file.c \ ffill.c \ fmt.c fmt.h \ @@ -70,5 +75,3 @@ libsb_common_a_SOURCES = \ sbapp.h \ smbas.h \ sys.h - - diff --git a/src/common/blib_func.c b/src/common/blib_func.c index 31ff8fd9..47f1deca 100644 --- a/src/common/blib_func.c +++ b/src/common/blib_func.c @@ -620,9 +620,6 @@ var_num_t cmd_math1(long funcCode, var_t *arg) { case kwRAD: r = x * M_PI / 180.0; break; - case kwCDBL: - r = x; - break; default: rt_raise("Unsupported built-in function call %ld", funcCode); r = 0.0; @@ -775,12 +772,6 @@ var_int_t cmd_imath1(long funcCode, var_t *arg) { IF_ERR_RETURN_0; switch (funcCode) { - case kwCINT: - // - // int <- CINT(float) - // - r = x; - break; case kwEOF: // // int <- EOF(file-handle) diff --git a/src/common/blib_graph.c b/src/common/blib_graph.c index 54f1bfe7..dce0cba2 100644 --- a/src/common/blib_graph.c +++ b/src/common/blib_graph.c @@ -1247,13 +1247,11 @@ var_t *par_getm3() { void m3combine(var_t *m, var_num_t nm[3][3]) { var_num_t om[3][3]; - int i, j; - var_t *e; // copy m to om - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) { - e = v_elem(m, (i * 3 + j)); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + var_t *e = v_elem(m, (i * 3 + j)); if (e->type == V_NUM) { om[i][j] = e->v.n; } else if (e->type == V_INT) { @@ -1265,9 +1263,9 @@ void m3combine(var_t *m, var_num_t nm[3][3]) { } // combine - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) { - e = v_elem(m, (i * 3 + j)); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + var_t *e = v_elem(m, (i * 3 + j)); if (e->type != V_NUM) { v_free(e); } @@ -1275,15 +1273,11 @@ void m3combine(var_t *m, var_num_t nm[3][3]) { e->v.n = nm[i][0] * om[0][j] + nm[i][1] * om[1][j] + nm[i][2] * om[2][j]; } } - } -// void m3ident(var_num_t m[3][3]) { - int i, j; - - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { m[i][j] = (i == j) ? 1.0 : 0.0; } } @@ -1293,16 +1287,10 @@ void m3ident(var_num_t m[3][3]) { // M3IDENT BYREF m3x3 // void cmd_m3ident() { - var_t *m, *e; - int i, j; - - m = par_getm3(); - if (prog_error) { - return; - } - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) { - e = v_elem(m, (i * 3 + j)); + var_t *m = par_getm3(); IF_PROG_ERR_RTN; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + var_t *e = v_elem(m, (i * 3 + j)); v_init(e); e->type = V_NUM; e->v.n = (i == j) ? 1.0 : 0.0; @@ -1318,32 +1306,15 @@ void cmd_m3rotate() { var_num_t angle, x = 0, y = 0, c, s; var_num_t matrix[3][3]; - m = par_getm3(); - if (prog_error) { - return; - } - par_getcomma(); - if (prog_error) { - return; - } - angle = par_getnum(); - if (prog_error) { - return; - } + m = par_getm3(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + angle = par_getnum(); IF_PROG_ERR_RTN; + if (code_peek() == kwTYPE_SEP) { - par_getcomma(); - if (prog_error) - return; - x = par_getnum(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - y = par_getnum(); - if (prog_error) { - return; - } + par_getcomma(); IF_PROG_ERR_RTN; + x = par_getnum(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + y = par_getnum(); IF_PROG_ERR_RTN; } c = cos(angle); @@ -1367,33 +1338,15 @@ void cmd_m3scale() { var_num_t x, y, fx, fy; var_num_t matrix[3][3]; - m = par_getm3(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - x = par_getnum(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - y = par_getnum(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - fx = par_getnum(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - fy = par_getnum(); - if (prog_error) - return; + m = par_getm3(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + x = par_getnum(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + y = par_getnum(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + fx = par_getnum();IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + fy = par_getnum();IF_PROG_ERR_RTN; m3ident(matrix); matrix[0][0] = fx; @@ -1411,21 +1364,11 @@ void cmd_m3translate() { var_num_t x, y; var_num_t matrix[3][3]; - m = par_getm3(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - x = par_getnum(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - y = par_getnum(); - if (prog_error) - return; + m = par_getm3(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + x = par_getnum(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + y = par_getnum(); IF_PROG_ERR_RTN; m3ident(matrix); matrix[2][0] = x; @@ -1439,22 +1382,21 @@ void cmd_m3translate() { void cmd_m3apply() { var_t *m, *p, *e; var_num_t om[3][3], x, y; - int i, j, count; - m = par_getm3(); - if (prog_error) - return; - par_getcomma(); - if (prog_error) - return; - p = par_getvarray(); - if (prog_error) + m = par_getm3(); IF_PROG_ERR_RTN; + par_getcomma(); IF_PROG_ERR_RTN; + p = par_getvarray(); IF_PROG_ERR_RTN; + + if (!p || p->type != V_ARRAY) { + err_varisnotarray(); return; - count = v_asize(p); + } + + int count = v_asize(p); // copy m to om - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { e = v_elem(m, i * 3 + j); om[i][j] = v_getreal(e); } @@ -1466,7 +1408,7 @@ void cmd_m3apply() { int o; count = (v_asize(p) >> 1); - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { o = i << 1; x = v_getreal(v_elem(p, o)); y = v_getreal(v_elem(p, o + 1)); @@ -1474,16 +1416,15 @@ void cmd_m3apply() { v_setreal(v_elem(p, o + 1), x * om[0][1] + y * om[1][1] + om[2][1]); } } else { - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { e = v_elem(p, i); - if (e->type != V_ARRAY) + if (e->type != V_ARRAY) { err_parsepoly(i, 10); - else if ((v_asize(e) % 2) != 0) + } else if ((v_asize(e) % 2) != 0) { err_parsepoly(i, 11); - - if (prog_error) - break; + } + IF_PROG_ERR_RTN; x = v_getreal(v_elem(e, 0)); y = v_getreal(v_elem(e, 1)); diff --git a/src/common/brun.c b/src/common/brun.c index 9e1022b6..3dd9f289 100644 --- a/src/common/brun.c +++ b/src/common/brun.c @@ -15,7 +15,7 @@ #include "common/blib.h" #include "common/str.h" #include "common/fmt.h" -#include "common/extlib.h" +#include "common/plugins.h" #include "common/units.h" #include "common/kw.h" #include "common/var.h" @@ -688,7 +688,7 @@ static inline void bc_loop_call_extp() { prog_error = gsb_last_error; } } else { - slib_procexec(lib, prog_symtable[idx].exp_idx); + plugin_procexec(lib, prog_symtable[idx].exp_idx); } } @@ -1132,12 +1132,12 @@ void brun_load_libraries(int tid) { // update lib-table's task-id field (in this code; not in lib's code) prog_libtable[i].tid = -1; // not a task int lib_id = prog_libtable[i].id; - slib_import(lib_id, 0); + plugin_open(prog_libtable[i].lib, lib_id); // update lib-symbols's task-id field (in this code; not in lib's code) for (int j = 0; j < prog_symcount; j++) { if (prog_symtable[j].lib_id == lib_id) { - prog_symtable[j].exp_idx = slib_get_kid(lib_id, prog_symtable[j].symbol); + prog_symtable[j].exp_idx = plugin_get_kid(lib_id, prog_symtable[j].symbol); prog_symtable[j].task_id = -1; } } @@ -1698,7 +1698,7 @@ int sbasic_main(const char *file) { // initialize managers init_tasks(); unit_mgr_init(); - slib_init(); + plugin_init(); if (prog_error) { success = 0; @@ -1707,7 +1707,7 @@ int sbasic_main(const char *file) { } // clean up managers - slib_close(); + plugin_close(); unit_mgr_close(); destroy_tasks(); diff --git a/src/common/device.c b/src/common/device.c index 00c4c364..a8d30f29 100644 --- a/src/common/device.c +++ b/src/common/device.c @@ -166,7 +166,7 @@ void dev_trace_line(int lineNo) { #ifndef IMPL_LOG_WRITE void lwrite(const char *buf) { - fprintf(stderr, "%s\n", buf); + fprintf(stderr, "%s", buf); } #endif diff --git a/src/common/eval.c b/src/common/eval.c index 3087c79c..a7b47e69 100644 --- a/src/common/eval.c +++ b/src/common/eval.c @@ -13,7 +13,7 @@ #include "common/kw.h" #include "common/blib.h" #include "common/device.h" -#include "common/extlib.h" +#include "common/plugins.h" #include "common/var_eval.h" #define IP prog_ip @@ -32,9 +32,9 @@ v_free((v)); \ } -/** - * matrix: convert var_t to double[r][c] - */ +// +// matrix: convert var_t to double[r][c] +// var_num_t *mat_toc(var_t *v, int32_t *rows, int32_t *cols) { var_num_t *m = NULL; *rows = *cols = 0; @@ -70,9 +70,9 @@ var_num_t *mat_toc(var_t *v, int32_t *rows, int32_t *cols) { return m; } -/** - * matrix: conv. double[nr][nc] to var_t - */ +// +// matrix: conv. double[nr][nc] to var_t +// void mat_tov(var_t *v, var_num_t *m, int rows, int cols, int protect_col1) { if (cols > 1 || protect_col1) { v_tomatrix(v, rows, cols); @@ -89,9 +89,9 @@ void mat_tov(var_t *v, var_num_t *m, int rows, int cols, int protect_col1) { } } -/** - * matrix: 1op - */ +// +// matrix: 1op +// void mat_op1(var_t *l, int op, var_num_t n) { int lr, lc; @@ -114,29 +114,33 @@ void mat_op1(var_t *l, int op, var_num_t n) { } } } - mat_tov(l, m, lr, lc, 1); + if (v_maxdim(l) == 1) { + mat_tov(l, m, lc, 1, 0); + } else { + mat_tov(l, m, lr, lc, 1); + } free(m1); free(m); } } -/** - * M = -A - */ +// +// M = -A +// void mat_antithetos(var_t *v) { mat_op1(v, 'A', 0); } -/** - * M = A - */ +// +// M = A +// void mat_mulN(var_t *v, var_num_t N) { mat_op1(v, '*', N); } -/** - * matrix - add/sub - */ +// +// matrix - add/sub +// void mat_op2(var_t *l, var_t *r, int op) { int lr, lc, rr, rc; @@ -157,8 +161,7 @@ void mat_op2(var_t *l, var_t *r, int op) { } else { m[pos] = m2[pos] - m1[pos]; } - // array is comming reversed because of - // where to store + // array is reversed because of where to store } } } @@ -187,9 +190,9 @@ void mat_sub(var_t *l, var_t *r) { mat_op2(l, r, '-'); } -/** - * matrix: multiply two 1d arrays - */ +// +// matrix: multiply two 1d arrays +// void mat_mul_1d(var_t *l, var_t *r) { uint32_t size = v_asize(l); for (uint32_t i = 0; i < size; i++) { @@ -200,9 +203,9 @@ void mat_mul_1d(var_t *l, var_t *r) { } } -/** - * matrix: dot product of two 1d arrays - */ +// +// matrix: dot product of two 1d arrays +// void mat_dot(var_t *l, var_t *r) { var_num_t result = 0; uint32_t size = v_asize(l); @@ -214,9 +217,9 @@ void mat_dot(var_t *l, var_t *r) { v_setreal(r, result); } -/** - * matrix: multiply - */ +// +// matrix: multiply +// void mat_mul(var_t *l, var_t *r) { int lr, lc, rr, rc; @@ -255,9 +258,9 @@ void mat_mul(var_t *l, var_t *r) { } } -/** - * The LIKE operator - */ +// +// The LIKE operator +// int v_wc_match(var_t *vwc, var_t *v) { int ri; @@ -826,7 +829,7 @@ static inline void eval_extf(var_t *r) { if (lib & UID_UNIT_BIT) { unit_exec(lib & (~UID_UNIT_BIT), idx, r); } else { - slib_funcexec(lib, prog_symtable[idx].exp_idx, r); + plugin_funcexec(lib, prog_symtable[idx].exp_idx, r); } } @@ -1144,7 +1147,6 @@ static inline void eval_callf(var_t *r) { case kwLOG10: case kwFIX: case kwINT: - case kwCDBL: case kwDEG: case kwRAD: case kwPENF: @@ -1155,7 +1157,6 @@ static inline void eval_callf(var_t *r) { break; case kwFRE: case kwSGN: - case kwCINT: case kwEOF: case kwSEEKF: case kwLOF: @@ -1219,9 +1220,9 @@ static inline void eval_callf(var_t *r) { } } -/** - * executes the expression (Code[IP]) and returns the result (r) - */ +// +// executes the expression (Code[IP]) and returns the result (r) +// void eval(var_t *r) { var_t *left = NULL; bcip_t eval_pos = eval_sp; diff --git a/src/common/extlib.c b/src/common/extlib.c deleted file mode 100644 index 377027c4..00000000 --- a/src/common/extlib.c +++ /dev/null @@ -1,622 +0,0 @@ -// This file is part of SmallBASIC -// -// SmallBASIC - External library support (plugins) -// -// This program is distributed under the terms of the GPL v2.0 or later -// Download the GNU Public License (GPL) from www.gnu.org -// -// Copyright(C) 2001 Nicholas Christopoulos - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "common/smbas.h" - -#if defined(__MINGW32__) - #include - #include - #define WIN_EXTLIB - #define LIB_EXT ".dll" -#elif defined(_UnixOS) - #include - #define LNX_EXTLIB - #define LIB_EXT ".so" -#endif - -#if defined(LNX_EXTLIB) || defined(WIN_EXTLIB) -#include "common/extlib.h" -#include "common/pproc.h" -#include - -#define MAX_SLIBS 64 -#define MAX_PARAM 16 -#define TABLE_GROW_SIZE 16 -#define NAME_SIZE 256 -#define PATH_SIZE 1024 - -typedef int (*sblib_exec_fn)(int, int, slib_par_t *, var_t *); -typedef int (*sblib_getname_fn) (int, char *); -typedef int (*sblib_count_fn) (void); -typedef int (*sblib_init_fn) (const char *); -typedef void (*sblib_close_fn) (void); -typedef const char *(*sblib_get_module_name_fn) (void); - -typedef struct { - char name[NAME_SIZE]; - char fullname[PATH_SIZE]; - void *handle; - sblib_exec_fn sblib_proc_exec; - sblib_exec_fn sblib_func_exec; - uint32_t id; - uint32_t flags; - uint8_t imported; - uint32_t proc_count; - uint32_t func_count; - uint32_t proc_list_size; - uint32_t func_list_size; - ext_func_node_t *func_list; - ext_proc_node_t *proc_list; -} slib_t; - -static slib_t slib_list[MAX_SLIBS]; -static uint32_t slib_count; - -#if defined(LNX_EXTLIB) -int slib_llopen(slib_t *lib) { - lib->handle = dlopen(lib->fullname, RTLD_NOW); - if (lib->handle == NULL) { - sc_raise("LIB: error on loading %s\n%s", lib->name, dlerror()); - } - return (lib->handle != NULL); -} - -void *slib_getoptptr(slib_t *lib, const char *name) { - return dlsym(lib->handle, name); -} - -int slib_llclose(slib_t *lib) { - if (!lib->handle) { - return 0; - } - dlclose(lib->handle); - lib->handle = NULL; - return 1; -} - -#elif defined(WIN_EXTLIB) -int slib_llopen(slib_t *lib) { - lib->handle = LoadLibraryA(lib->fullname); - if (lib->handle == NULL) { - int error = GetLastError(); - switch (error) { - case ERROR_MOD_NOT_FOUND: - sc_raise("LIB: DLL dependency error [%d] loading %s [%s]\n", error, lib->fullname, lib->name); - break; - case ERROR_DYNLINK_FROM_INVALID_RING: - sc_raise("LIB: DLL build error [%d] loading %s [%s]\n", error, lib->fullname, lib->name); - break; - default: - sc_raise("LIB: error [%d] loading %s [%s]\n", error, lib->fullname, lib->name); - break; - } - } - return (lib->handle != NULL); -} - -void *slib_getoptptr(slib_t *lib, const char *name) { - return GetProcAddress((HMODULE) lib->handle, name); -} - -int slib_llclose(slib_t *lib) { - if (!lib->handle) { - return 0; - } - FreeLibrary(lib->handle); - lib->handle = NULL; - return 1; -} -#endif - -/** - * returns slib_t* for the given id - */ -slib_t *get_lib(int lib_id) { - if (lib_id < 0 || lib_id >= slib_count) { - return NULL; - } - return &slib_list[lib_id]; -} - -/** - * add an external procedure to the list - */ -int slib_add_external_proc(const char *proc_name, int lib_id) { - slib_t *lib = get_lib(lib_id); - - if (lib->proc_list == NULL) { - lib->proc_list_size = TABLE_GROW_SIZE; - lib->proc_list = (ext_proc_node_t *)malloc(sizeof(ext_proc_node_t) * lib->proc_list_size); - } else if (lib->proc_list_size <= (lib->proc_count + 1)) { - lib->proc_list_size += TABLE_GROW_SIZE; - lib->proc_list = (ext_proc_node_t *)realloc(lib->proc_list, sizeof(ext_proc_node_t) * lib->proc_list_size); - } - - lib->proc_list[lib->proc_count].lib_id = lib_id; - lib->proc_list[lib->proc_count].symbol_index = 0; - strlcpy(lib->proc_list[lib->proc_count].name, proc_name, sizeof(lib->proc_list[lib->proc_count].name)); - strupper(lib->proc_list[lib->proc_count].name); - - if (opt_verbose) { - log_printf("LIB: %d, Idx: %d, PROC '%s'\n", lib_id, lib->proc_count, - lib->proc_list[lib->proc_count].name); - } - lib->proc_count++; - return lib->proc_count - 1; -} - -/** - * Add an external function to the list - */ -int slib_add_external_func(const char *func_name, uint32_t lib_id) { - slib_t *lib = get_lib(lib_id); - - if (lib->func_list == NULL) { - lib->func_list_size = TABLE_GROW_SIZE; - lib->func_list = (ext_func_node_t *)malloc(sizeof(ext_func_node_t) * lib->func_list_size); - } else if (lib->func_list_size <= (lib->func_count + 1)) { - lib->func_list_size += TABLE_GROW_SIZE; - lib->func_list = (ext_func_node_t *) - realloc(lib->func_list, sizeof(ext_func_node_t) * lib->func_list_size); - } - - lib->func_list[lib->func_count].lib_id = lib_id; - lib->func_list[lib->func_count].symbol_index = 0; - strlcpy(lib->func_list[lib->func_count].name, func_name, sizeof(lib->func_list[lib->func_count].name)); - strupper(lib->func_list[lib->func_count].name); - - if (opt_verbose) { - log_printf("LIB: %d, Idx: %d, FUNC '%s'\n", lib_id, lib->func_count, - lib->func_list[lib->func_count].name); - } - lib->func_count++; - return lib->func_count - 1; -} - -/** - * returns the ID of the keyword - */ -int slib_get_kid(int lib_id, const char *name) { - slib_t *lib = get_lib(lib_id); - if (lib != NULL) { - const char *dot = strchr(name, '.'); - const char *field = (dot != NULL ? dot + 1 : name); - for (int i = 0; i < lib->proc_count; i++) { - if (lib->proc_list[i].lib_id == lib_id && - strcmp(lib->proc_list[i].name, field) == 0) { - return i; - } - } - for (int i = 0; i < lib->func_count; i++) { - if (lib->func_list[i].lib_id == lib_id && - strcmp(lib->func_list[i].name, field) == 0) { - return i; - } - } - } - return -1; -} - -/** - * returns the library-id (index of library of the current process) - */ -int slib_get_module_id(const char *name, const char *alias) { - for (int i = 0; i < slib_count; i++) { - slib_t *lib = &slib_list[i]; - if (strcasecmp(lib->name, name) == 0) { - strcpy(lib->name, alias); - return i; - } - } - // not found - return -1; -} - -void slib_import_routines(slib_t *lib, int comp) { - int total = 0; - char buf[SB_KEYWORD_SIZE]; - - lib->sblib_func_exec = slib_getoptptr(lib, "sblib_func_exec"); - lib->sblib_proc_exec = slib_getoptptr(lib, "sblib_proc_exec"); - sblib_count_fn fcount = slib_getoptptr(lib, "sblib_proc_count"); - sblib_getname_fn fgetname = slib_getoptptr(lib, "sblib_proc_getname"); - - if (fcount && fgetname) { - int count = fcount(); - total += count; - for (int i = 0; i < count; i++) { - if (fgetname(i, buf)) { - strupper(buf); - if (!lib->imported && slib_add_external_proc(buf, lib->id) == -1) { - break; - } else if (comp) { - char name[NAME_SIZE]; - strlcpy(name, lib->name, sizeof(name)); - strlcat(name, ".", sizeof(name)); - strlcat(name, buf, sizeof(name)); - strupper(name); - comp_add_external_proc(name, lib->id); - } - } - } - } - - fcount = slib_getoptptr(lib, "sblib_func_count"); - fgetname = slib_getoptptr(lib, "sblib_func_getname"); - - if (fcount && fgetname) { - int count = fcount(); - total += count; - for (int i = 0; i < count; i++) { - if (fgetname(i, buf)) { - strupper(buf); - if (!lib->imported && slib_add_external_func(buf, lib->id) == -1) { - break; - } else if (comp) { - char name[NAME_SIZE]; - strlcpy(name, lib->name, sizeof(name)); - strlcat(name, ".", sizeof(name)); - strlcat(name, buf, sizeof(name)); - strupper(name); - comp_add_external_func(name, lib->id); - } - } - } - } - - if (!total) { - log_printf("LIB: module '%s' has no exports\n", lib->name); - } -} - -/** - * updates compiler with the module's keywords - */ -void slib_import(int lib_id, int comp) { - slib_t *lib = get_lib(lib_id); - if (lib && (comp || !lib->imported)) { - slib_import_routines(lib, comp); - lib->imported = 1; - } - if (lib && !comp) { - sblib_init_fn minit = slib_getoptptr(lib, "sblib_init"); - if (minit && !minit(gsb_last_file)) { - rt_raise("LIB: %s->sblib_init(), failed", lib->name); - } - } -} - -/** - * opens the library - */ -void slib_open(const char *fullname, const char *name) { - int name_index = 0; - - if (strncmp(name, "lib", 3) == 0) { - // libmysql -> store mysql - name_index = 3; - } - - slib_t *lib = &slib_list[slib_count]; - memset(lib, 0, sizeof(slib_t)); - strlcpy(lib->name, name + name_index, NAME_SIZE); - strlcpy(lib->fullname, fullname, PATH_SIZE); - lib->id = slib_count; - lib->imported = 0; - - if (!opt_quiet) { - log_printf("LIB: registering '%s'", fullname); - } - if (slib_llopen(lib)) { - slib_count++; - // override default name - sblib_get_module_name_fn get_module_name = slib_getoptptr(lib, "sblib_get_module_name"); - if (get_module_name) { - strlcpy(lib->name, get_module_name(), NAME_SIZE); - } - } else { - sc_raise("LIB: can't open %s", fullname); - } -} - -/** - * whether name ends with LIB_EXT and does not contain '-', eg libstdc++-6.dll - */ -int slib_is_module(const char *name) { - int result = 0; - if (name && name[0] != '\0') { - int offs = strlen(name) - (sizeof(LIB_EXT) - 1); - result = offs > 0 && strchr(name, '-') == NULL && strcasecmp(name + offs, LIB_EXT) == 0; - } - return result; -} - -void slib_open_path(const char *path, const char *name) { - if (slib_is_module(name)) { - // ends with LIB_EXT - char full[PATH_SIZE]; - char libname[NAME_SIZE]; - - // copy name without extension - strlcpy(libname, name, sizeof(libname)); - char *p = strchr(libname, '.'); - *p = '\0'; - - // copy full path to name - strlcpy(full, path, sizeof(full)); - if (path[strlen(path) - 1] != '/') { - // add trailing separator - strlcat(full, "/", sizeof(full)); - } - strlcat(full, name, sizeof(full)); - slib_open(full, libname); - } -} - -void slib_scan_path(const char *path) { - struct stat stbuf; - if (stat(path, &stbuf) != -1) { - if (S_ISREG(stbuf.st_mode)) { - char *name = strrchr(path, '/'); - slib_open_path(path, (name ? name + 1 : path)); - } else { - DIR *dp = opendir(path); - if (dp != NULL) { - struct dirent *e; - while ((e = readdir(dp)) != NULL) { - char *name = e->d_name; - if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) { - slib_open_path(path, name); - } - } - closedir(dp); - } - } - } else if (!opt_quiet) { - log_printf("LIB: module path '%s' not found.\n", path); - } -} - -void slib_init_path() { - char *path = opt_modpath; - while (path && path[0] != '\0') { - char *sep = strchr(path, ':'); - if (sep) { - // null terminate the current path - *sep = '\0'; - slib_scan_path(path); - *sep = ':'; - path = sep + 1; - } else { - slib_scan_path(path); - path = NULL; - } - } -} - -/** - * slib-manager: initialize manager - */ -void slib_init() { - slib_count = 0; - - if (!prog_error && opt_loadmod) { - if (opt_modpath[0] == '\0') { - const char *modpath = getenv("SBASICPATH"); - if (modpath != NULL) { - strlcpy(opt_modpath, modpath, OPT_MOD_SZ); - } - } - if (!opt_quiet) { - log_printf("LIB: scanning for modules in '%s'\n", opt_modpath); - } - slib_init_path(); - } -} - -/** - * slib-manager: close everything - */ -void slib_close() { - for (int i = 0; i < slib_count; i++) { - slib_t *lib = &slib_list[i]; - if (lib->handle) { - sblib_close_fn mclose = slib_getoptptr(lib, "sblib_close"); - if (mclose) { - mclose(); - } - slib_llclose(lib); - } - free(lib->proc_list); - free(lib->func_list); - lib->proc_count = 0; - lib->func_count = 0; - lib->proc_list_size = 0; - lib->func_list_size = 0; - lib->func_list = NULL; - lib->proc_list = NULL; - } -} - -/** - * build parameter table - */ -int slib_build_ptable(slib_par_t *ptable) { - int pcount = 0; - var_t *arg; - bcip_t ofs; - - if (code_peek() == kwTYPE_LEVEL_BEGIN) { - code_skipnext(); - byte ready = 0; - do { - byte code = code_peek(); - switch (code) { - case kwTYPE_EOC: - code_skipnext(); - break; - case kwTYPE_SEP: - code_skipsep(); - break; - case kwTYPE_LEVEL_END: - ready = 1; - break; - case kwTYPE_VAR: - // variable - ofs = prog_ip; - if (code_isvar()) { - // push parameter - ptable[pcount].var_p = code_getvarptr(); - ptable[pcount].byref = 1; - pcount++; - break; - } - - // restore IP - prog_ip = ofs; - // no 'break' here - default: - // default --- expression (BYVAL ONLY) - arg = v_new(); - eval(arg); - if (!prog_error) { - // push parameter - ptable[pcount].var_p = arg; - ptable[pcount].byref = 0; - pcount++; - } else { - v_free(arg); - v_detach(arg); - return pcount; - } - } - if (pcount == MAX_PARAM) { - err_parm_limit(MAX_PARAM); - } - } while (!ready && !prog_error); - // kwTYPE_LEVEL_END - code_skipnext(); - } - return pcount; -} - -/** - * free parameter table - */ -void slib_free_ptable(slib_par_t *ptable, int pcount) { - for (int i = 0; i < pcount; i++) { - if (ptable[i].byref == 0) { - v_free(ptable[i].var_p); - v_detach(ptable[i].var_p); - } - } -} - -/** - * execute a function or procedure - */ -int slib_exec(slib_t *lib, var_t *ret, int index, int proc) { - slib_par_t *ptable; - int pcount; - if (code_peek() == kwTYPE_LEVEL_BEGIN) { - ptable = (slib_par_t *)malloc(sizeof(slib_par_t) * MAX_PARAM); - pcount = slib_build_ptable(ptable); - } else { - ptable = NULL; - pcount = 0; - } - if (prog_error) { - slib_free_ptable(ptable, pcount); - free(ptable); - return 0; - } - - int success; - v_init(ret); - if (proc) { - success = lib->sblib_proc_exec(index, pcount, ptable, ret); - } else { - success = lib->sblib_func_exec(index, pcount, ptable, ret); - } - - // error - if (!success) { - if (ret->type == V_STR) { - err_throw("LIB:%s: %s\n", lib->name, ret->v.p.ptr); - } else { - err_throw("LIB:%s: Unspecified error calling %s\n", lib->name, (proc ? "SUB" : "FUNC")); - } - } - - // clean-up - if (ptable) { - slib_free_ptable(ptable, pcount); - free(ptable); - } - - return success; -} - -/** - * execute a procedure - */ -int slib_procexec(int lib_id, int index) { - int result; - slib_t *lib = get_lib(lib_id); - if (lib && lib->sblib_proc_exec) { - var_t ret; - v_init(&ret); - result = slib_exec(lib, &ret, index, 1); - v_free(&ret); - } else { - result = 0; - } - return result; -} - -/** - * execute a function - */ -int slib_funcexec(int lib_id, int index, var_t *ret) { - int result; - slib_t *lib = get_lib(lib_id); - if (lib && lib->sblib_func_exec) { - result = slib_exec(lib, ret, index, 0); - } else { - result = 0; - } - return result; -} - -void *slib_get_func(const char *name) { - void *result = NULL; - for (int i = 0; i < slib_count && result == NULL; i++) { - slib_t *lib = &slib_list[i]; - if (lib->imported) { - result = slib_getoptptr(lib, name); - } - } - return result; -} - -#else -// dummy implementations -int slib_funcexec(int lib_id, int index, var_t *ret) { return 0; } -int slib_procexec(int lib_id, int index) { return 0; } -int slib_get_kid(int lib_id, const char *name) { return -1; } -int slib_get_module_id(const char *name, const char *alias) { return -1; } -void slib_close() {} -void slib_init(int mcount, const char *mlist) {} -void *slib_get_func(const char *name) { return 0; } -void slib_import(int lib_id, int comp) {} -#endif diff --git a/src/common/extlib.h b/src/common/extlib.h deleted file mode 100644 index bfdec042..00000000 --- a/src/common/extlib.h +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of SmallBASIC -// -// SmallBASIC plugin manager -// -// This program is distributed under the terms of the GPL v2.0 or later -// Download the GNU Public License (GPL) from www.gnu.org -// -// Copyright(C) 2000 Nicholas Christopoulos - -/** - * @ingroup mod - * @page moddoc Modules - * - * The modules are common shared-libraries which can call back the SB's code. - * - * The module-manager loads the modules at the startup and unloads them - * before SB's exit. Which module will be loaded is specified by the - * user in SB's command-line parameters (option -m). The path of the modules - * is predefined to /usr/lib/sbasic/modules and/or /usr/local/lib/sbasic/modules - * - * Standard interface - * - * All modules must provides at least the three following functions: - * sblib_init(), sblib_close, sblib_type(). - * - * the sblib_init() called on startup, the sblib_close() called on SB's exit, - * and the sblib_type() called after sblib_init() to inform the module-manager - * about the type of the module (slib_tp). - * - * Library-modules - * - * The SB before compiles the .bas program, the module-manager - * asks every module about the number of the functions and procedures - * which are supported. (sblib_proc_count, sblib_func_count) - * - * It continues by asking the name of each function and procedure and - * updates the compiler. (sblib_proc_getname, sblib_func_getname) - * - * On the execution time, if the program wants to execute a library's - * procedure or function, the module-manager builds the parameter table - * and calls the sblib_proc_exec or the sblib_func_exec. - * - * See modules/example1.c - * Notes: - * - * Procedure & functions names are limited to 32 characters (33 with \0) - */ - -/** - * @defgroup mod Module Manager - */ -/** - * @defgroup modstd Module interface - Standard - */ -/** - * @defgroup modlib Module interface - Library - */ - -#if !defined(_sb_extlib_h) -#define _sb_extlib_h - -#include "common/sys.h" -#include "common/var.h" -#include "common/device.h" -#include "include/module.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/** - * @ingroup mod - * - * Initialize module-manager - * - * default path /usr/lib/sbasic/modules/:/usr/local/lib/sbasic/modules/ - */ -void slib_init(); - -/** - * @ingroup mod - * - * close module manager - */ -void slib_close(void); - -/** - * @ingroup mod - * - * set the alias and returns a library's ID - * - * @param name is the name of the library (without the file-extention) - * @param alias updates the internal name with the given alias - * @return the id or -1 for error - */ -int slib_get_module_id(const char *name, const char *alias); - -/** - * @ingroup mod - * - * imports the modules routine and optionally updates the compiler - * with the module (mid) keywords. - */ -void slib_import(int lib_id, int comp); - -/** - * @ingroup mod - * - * returns the ID of the keyword. used at run-time to assign BCs ID with slib_mgr's one - */ -int slib_get_kid(int lib_id, const char *name); - -/** - * @ingroup mod - * - * execute a library's procedure - * - * @param lib is the lib-id - * @param index is the index of the procedure - * @return non-zero on success - */ -int slib_procexec(int lib, int index); - -/** - * @ingroup mod - * - * execute a library's function - * - * @param lib is the lib-id - * @param index is the index of the function - * @param ret is the variable to store the result - * @return non-zero on success - */ -int slib_funcexec(int lib, int index, var_t *ret); - -/** - * @ingroup mod - * - * returns the function from the first available module - * - * @param name the function name - * @return non-zero on success - */ -void *slib_get_func(const char *name); - -#if defined(__cplusplus) -} -#endif -#endif diff --git a/src/common/file.c b/src/common/file.c index d6dae52d..175d3a0b 100644 --- a/src/common/file.c +++ b/src/common/file.c @@ -10,7 +10,6 @@ #include "common/sys.h" #include "common/device.h" #include "common/pproc.h" -#include "common/extlib.h" #include "common/messages.h" #include @@ -158,7 +157,19 @@ int dev_fopen(int sb_handle, const char *name, int flags) { f->handle = -1; f->open_flags = flags; f->drv_data = NULL; - strlcpy(f->name, name, sizeof(f->name)); + + if (name[0] == '~') { + if (getenv("HOME")) { + strlcpy(f->name, getenv("HOME"), sizeof(f->name)); + } else { + strlcpy(f->name, getenv("HOMEDRIVE"), sizeof(f->name)); + strlcpy(f->name, getenv("HOMEPATH"), sizeof(f->name)); + } + strlcpy(f->name, "/", sizeof(f->name)); + strlcpy(f->name, name + 1, sizeof(f->name)); + } else { + strlcpy(f->name, name, sizeof(f->name)); + } f->type = ft_stream; diff --git a/src/common/fs_socket_client.c b/src/common/fs_socket_client.c index a2cbc5a4..ed7188d9 100644 --- a/src/common/fs_socket_client.c +++ b/src/common/fs_socket_client.c @@ -39,7 +39,9 @@ int sockcl_open(dev_file_t *f) { return 1; } +// // open a web server connection +// int http_open(dev_file_t *f) { char host[250]; char txbuf[1024]; @@ -101,10 +103,10 @@ int http_open(dev_file_t *f) { } sprintf(txbuf, "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" - "Accept: */*\r\n" - "Accept-Language: en-au\r\n" - "User-Agent: SmallBASIC\r\n", slash ? slash : "/", host); + "Host: %s\r\n" + "Accept: */*\r\n" + "Accept-Language: en-au\r\n" + "User-Agent: SmallBASIC\r\n", slash ? slash : "/", host); if (f->drv_dw[2]) { // If-Modified-Since: Sun, 03 Apr 2005 04:45:47 GMT strcat(txbuf, "If-Modified-Since: "); @@ -116,70 +118,91 @@ int http_open(dev_file_t *f) { return 1; } +// // read from a web server connection +// int http_read(dev_file_t *f, var_t *var_p) { + static const char *delim = "\r\n\r\n"; char rxbuff[1024]; - int inHeader = 1; + int inContent = 0; int httpOK = 0; - v_free(var_p); - var_p->type = V_STR; - var_p->v.p.ptr = 0; - var_p->v.p.length = 0; + v_setint(var_p, 0); while (1) { - int bytes = net_read(f->handle, (char *) rxbuff, sizeof(rxbuff)); + int bytes = net_read(f->handle, (char *)rxbuff, sizeof(rxbuff)); if (bytes == -1) { httpOK = 0; break; } else if (bytes == 0) { - break; // no more data + // no more data + break; } // assumes http header < 1024 bytes - if (inHeader) { + if (inContent) { + if (var_p->type == V_INT) { + v_free(var_p); + var_p->type = V_STR; + var_p->v.p.length = bytes; + var_p->v.p.ptr = malloc(var_p->v.p.length + 1); + var_p->v.p.owner = 1; + memcpy(var_p->v.p.ptr, rxbuff, var_p->v.p.length); + var_p->v.p.ptr[var_p->v.p.length] = '\0'; + } else { + var_p->v.p.ptr = realloc(var_p->v.p.ptr, var_p->v.p.length + bytes + 1); + memcpy(var_p->v.p.ptr + var_p->v.p.length, rxbuff, bytes); + var_p->v.p.length += bytes; + var_p->v.p.ptr[var_p->v.p.length] = '\0'; + } + } else { int i = 0; - while (1) { - int iattr = i; - while (rxbuff[i] != 0 && rxbuff[i] != '\n') { - i++; - } - if (rxbuff[i] == 0) { - inHeader = 0; - break; // no end delimiter + int countNL = 0; + while (i < bytes && rxbuff[i] != 0 && countNL != 4) { + // scan for CR + LF + CR + LF + if (rxbuff[i] == delim[countNL]) { + countNL++; + } else { + countNL = 0; } - if (rxbuff[i + 2] == '\n') { - var_p->v.p.length = bytes - i - 3; + i++; + } + if (countNL == 4) { + // found start of content + if (i < bytes) { + // copy remaining characters from rxbuff + v_free(var_p); + var_p->type = V_STR; + var_p->v.p.length = bytes - i; var_p->v.p.ptr = malloc(var_p->v.p.length + 1); var_p->v.p.owner = 1; - memcpy(var_p->v.p.ptr, rxbuff + i + 3, var_p->v.p.length); - var_p->v.p.ptr[var_p->v.p.length] = 0; - inHeader = 0; - break; // found start of content - } - // null terminate attribute (in \r) - rxbuff[i - 1] = 0; - i++; - if (strstr(rxbuff + iattr, "200 OK") != 0) { - httpOK = 1; + memcpy(var_p->v.p.ptr, rxbuff + i, var_p->v.p.length); + var_p->v.p.ptr[var_p->v.p.length] = '\0'; } - if (strncmp(rxbuff + iattr, "Location: ", 10) == 0) { - // handle redirection + inContent = 1; + } + // null terminate headers fragment + rxbuff[i - 1] = '\0'; + if (strstr(rxbuff, "200 OK") != 0) { + httpOK = 1; + } + char *location = strstr(rxbuff, "Location: "); + if (location) { + // handle redirection + char *cr = strstr(location, "\r"); + if (cr) { + *cr = '\0'; sockcl_close(f); - strlcpy(f->name, rxbuff + iattr + 10, sizeof(f->name)); + strlcpy(f->name, location + 10, sizeof(f->name)); if (http_open(f) == 0) { - return 0; + httpOK = 0; + break; } - break; // scan next header + inContent = 0; + v_setint(var_p, 0); } } - } else { - var_p->v.p.ptr = realloc(var_p->v.p.ptr, var_p->v.p.length + bytes + 1); - memcpy(var_p->v.p.ptr + var_p->v.p.length, rxbuff, bytes); - var_p->v.p.length += bytes; - var_p->v.p.ptr[var_p->v.p.length] = 0; } } - return httpOK; } @@ -190,17 +213,17 @@ int sockcl_close(dev_file_t *f) { return 1; } -/* - * write to a socket - */ +// +// write to a socket +// int sockcl_write(dev_file_t *f, byte *data, uint32_t size) { net_send((socket_t) (long) f->handle, (char *)data, size); return size; } -/* - * read from a socket - */ +// +// read from a socket +// int sockcl_read(dev_file_t *f, byte *data, uint32_t size) { int result; if (f->handle != -1) { @@ -214,16 +237,16 @@ int sockcl_read(dev_file_t *f, byte *data, uint32_t size) { return result; } -/* - * Returns true (EOF) if the connection is broken - */ +// +// Returns true (EOF) if the connection is broken +// int sockcl_eof(dev_file_t *f) { return (((long) f->drv_dw[0]) <= 0) ? 1 : 0; } -/* - * returns the size of the data which are waiting in stream's queue - */ +// +// returns the size of the data which are waiting in stream's queue +// int sockcl_length(dev_file_t *f) { return net_peek((socket_t) (long) f->handle); } diff --git a/src/common/kw.h b/src/common/kw.h index e0b607a3..7af0dd8c 100644 --- a/src/common/kw.h +++ b/src/common/kw.h @@ -329,7 +329,6 @@ enum func_keywords { kwLOG10, kwFIX, kwINT, - kwCDBL, kwDEG, kwRAD, kwPENF, @@ -338,7 +337,6 @@ enum func_keywords { kwFRAC, kwFRE, kwSGN, - kwCINT, kwEOF, kwSEEKF, kwLOF, diff --git a/src/common/plugins.c b/src/common/plugins.c new file mode 100644 index 00000000..b97452e0 --- /dev/null +++ b/src/common/plugins.c @@ -0,0 +1,623 @@ +// This file is part of SmallBASIC +// +// SmallBASIC - External library support (plugins) +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// +// Copyright(C) 2001 Nicholas Christopoulos + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "common/smbas.h" + +#if defined(__MINGW32__) +#include +#include +#define WIN_EXTLIB +#define LIB_EXT ".dll" +#elif defined(_UnixOS) +#include +#define LNX_EXTLIB +#define LIB_EXT ".so" +#endif + +#if defined(LNX_EXTLIB) || defined(WIN_EXTLIB) +#include "common/plugins.h" +#include "common/pproc.h" +#include + +#define MAX_SLIBS 64 +#define MAX_PARAM 16 +#define TABLE_GROW_SIZE 16 +#define NAME_SIZE 256 +#define PATH_SIZE 1024 + +typedef int (*sblib_exec_fn)(int, int, slib_par_t *, var_t *); +typedef int (*sblib_getname_fn) (int, char *); +typedef int (*sblib_count_fn) (void); +typedef int (*sblib_init_fn) (const char *); +typedef void (*sblib_close_fn) (void); + +typedef struct { + char _fullname[PATH_SIZE]; + char _name[NAME_SIZE]; + void *_handle; + sblib_exec_fn _sblib_proc_exec; + sblib_exec_fn _sblib_func_exec; + ext_func_node_t *_func_list; + ext_proc_node_t *_proc_list; + uint32_t _id; + uint32_t _flags; + uint32_t _proc_count; + uint32_t _func_count; + uint32_t _proc_list_size; + uint32_t _func_list_size; + uint8_t _imported; +} slib_t; + +static slib_t *plugins[MAX_SLIBS]; + +#if defined(LNX_EXTLIB) +int slib_llopen(slib_t *lib) { + lib->_handle = dlopen(lib->_fullname, RTLD_NOW); + if (lib->_handle == NULL) { + sc_raise("LIB: error on loading %s\n%s", lib->_name, dlerror()); + } + return (lib->_handle != NULL); +} + +void *slib_getoptptr(slib_t *lib, const char *name) { + return dlsym(lib->_handle, name); +} + +static int slib_llclose(slib_t *lib) { + if (!lib->_handle) { + return 0; + } + dlclose(lib->_handle); + lib->_handle = NULL; + return 1; +} + +#elif defined(WIN_EXTLIB) +static int slib_llopen(slib_t *lib) { + lib->_handle = LoadLibraryA(lib->_fullname); + if (lib->_handle == NULL) { + int error = GetLastError(); + switch (error) { + case ERROR_MOD_NOT_FOUND: + sc_raise("LIB: DLL dependency error [%d] loading %s [%s]\n", error, lib->_fullname, lib->_name); + break; + case ERROR_DYNLINK_FROM_INVALID_RING: + sc_raise("LIB: DLL build error [%d] loading %s [%s]\n", error, lib->_fullname, lib->_name); + break; + default: + sc_raise("LIB: error [%d] loading %s [%s]\n", error, lib->_fullname, lib->_name); + break; + } + } + return (lib->_handle != NULL); +} + +static void *slib_getoptptr(slib_t *lib, const char *name) { + return GetProcAddress((HMODULE) lib->_handle, name); +} + +static int slib_llclose(slib_t *lib) { + if (!lib->_handle) { + return 0; + } + FreeLibrary(lib->_handle); + lib->_handle = NULL; + return 1; +} +#endif + +// +// returns slib_t* for the given id +// +static slib_t *get_lib(int lib_id) { + if (lib_id < 0 || lib_id >= MAX_SLIBS) { + return NULL; + } + return plugins[lib_id]; +} + +// +// opens the library +// +static int slib_open(const char *path, const char *name, const char *alias, int id) { + char fullname[PATH_SIZE]; + + strlcpy(fullname, path, sizeof(fullname)); + if (access(fullname, R_OK) != 0) { + if (path[strlen(path) - 1] != '/') { + // add trailing separator + strlcat(fullname, "/", sizeof(fullname)); + } + strlcat(fullname, name, sizeof(fullname)); + } + + slib_t *lib = plugins[id]; + memset(lib, 0, sizeof(slib_t)); + strlcpy(lib->_name, alias, NAME_SIZE); + strlcpy(lib->_fullname, fullname, PATH_SIZE); + lib->_id = id; + lib->_imported = 0; + + if (!opt_quiet) { + log_printf("LIB: registering '%s'", fullname); + } + + return slib_llopen(lib); +} + +// +// init path and file from the given name +// +static void slib_init_path(const char *name, char *path, char *file) { + if (name[0] == '"') { + // use quoted string to specify full system path + strlcpy(path, name + 1, PATH_SIZE); + int len = strlen(path); + if (len) { + path[len - 1] = '\0'; + } + // separate file-name from path + char *slash = strrchr(path, '/'); + if (slash) { + strlcpy(file, slash + 1, PATH_SIZE); + *slash = '\0'; + } + } else { + // non-quoted string to specify portable name + path[0] = '\0'; + char *slash = strrchr(name, '/'); + strlcpy(file, "lib", PATH_SIZE); + if (strcmp(name, "android") == 0) { + strlcat(file, "smallbasic", PATH_SIZE); + } else if (slash) { + *slash = '\0'; + strlcat(file, slash + 1, PATH_SIZE); + strlcpy(path, name, PATH_SIZE); + } else { + strlcat(file, name, PATH_SIZE); + } + strlcat(file, LIB_EXT, PATH_SIZE); + } +} + +// +// locate the file int the given path or standard locations +// +static int slib_find_path(char *path, const char *file) { + int result = 0; + // find in path + if (path[0]) { + result = sys_search_path(path, file, path); + } + // find in SBASICPATH + if (!result && getenv("SBASICPATH")) { + result = sys_search_path(getenv("SBASICPATH"), file, path); + if (!result && path[0]) { + char rel_path[PATH_SIZE]; + strlcpy(rel_path, getenv("SBASICPATH"), PATH_SIZE); + strlcat(rel_path, "/", PATH_SIZE); + strlcat(rel_path, path, PATH_SIZE); + result = sys_search_path(rel_path, file, path); + } + } + // find in modpath + if (!result && opt_modpath[0]) { + result = sys_search_path(opt_modpath, file, path); + if (!result && path[0]) { + char rel_path[PATH_SIZE]; + strlcpy(rel_path, opt_modpath, PATH_SIZE); + strlcat(rel_path, "/", PATH_SIZE); + strlcat(rel_path, path, PATH_SIZE); + result = sys_search_path(rel_path, file, path); + } + } + // find in program launch directory + if (!result && gsb_bas_dir[0]) { + result = sys_search_path(gsb_bas_dir, file, path); + } + if (!result) { + // find in current directory + result = sys_search_path(".", file, path); + } + + return result; +} + +// +// add an external procedure to the list +// +static int slib_add_external_proc(const char *proc_name, int lib_id) { + slib_t *lib = get_lib(lib_id); + + if (lib->_proc_list == NULL) { + lib->_proc_list_size = TABLE_GROW_SIZE; + lib->_proc_list = (ext_proc_node_t *)malloc(sizeof(ext_proc_node_t) * lib->_proc_list_size); + } else if (lib->_proc_list_size <= (lib->_proc_count + 1)) { + lib->_proc_list_size += TABLE_GROW_SIZE; + lib->_proc_list = (ext_proc_node_t *)realloc(lib->_proc_list, sizeof(ext_proc_node_t) * lib->_proc_list_size); + } + + lib->_proc_list[lib->_proc_count].lib_id = lib_id; + lib->_proc_list[lib->_proc_count].symbol_index = 0; + strlcpy(lib->_proc_list[lib->_proc_count].name, proc_name, sizeof(lib->_proc_list[lib->_proc_count].name)); + strupper(lib->_proc_list[lib->_proc_count].name); + + if (opt_verbose) { + log_printf("LIB: %d, Idx: %d, PROC '%s'\n", lib_id, lib->_proc_count, + lib->_proc_list[lib->_proc_count].name); + } + lib->_proc_count++; + return lib->_proc_count - 1; +} + +// +// Add an external function to the list +// +static int slib_add_external_func(const char *func_name, uint32_t lib_id) { + slib_t *lib = get_lib(lib_id); + + if (lib->_func_list == NULL) { + lib->_func_list_size = TABLE_GROW_SIZE; + lib->_func_list = (ext_func_node_t *)malloc(sizeof(ext_func_node_t) * lib->_func_list_size); + } else if (lib->_func_list_size <= (lib->_func_count + 1)) { + lib->_func_list_size += TABLE_GROW_SIZE; + lib->_func_list = (ext_func_node_t *) + realloc(lib->_func_list, sizeof(ext_func_node_t) * lib->_func_list_size); + } + + lib->_func_list[lib->_func_count].lib_id = lib_id; + lib->_func_list[lib->_func_count].symbol_index = 0; + strlcpy(lib->_func_list[lib->_func_count].name, func_name, sizeof(lib->_func_list[lib->_func_count].name)); + strupper(lib->_func_list[lib->_func_count].name); + + if (opt_verbose) { + log_printf("LIB: %d, Idx: %d, FUNC '%s'\n", lib_id, lib->_func_count, + lib->_func_list[lib->_func_count].name); + } + lib->_func_count++; + return lib->_func_count - 1; +} + +// +// import functions from the external library +// +static void slib_import_routines(slib_t *lib, int comp) { + int total = 0; + char buf[SB_KEYWORD_SIZE]; + + lib->_sblib_func_exec = slib_getoptptr(lib, "sblib_func_exec"); + lib->_sblib_proc_exec = slib_getoptptr(lib, "sblib_proc_exec"); + sblib_count_fn fcount = slib_getoptptr(lib, "sblib_proc_count"); + sblib_getname_fn fgetname = slib_getoptptr(lib, "sblib_proc_getname"); + + if (fcount && fgetname) { + int count = fcount(); + total += count; + for (int i = 0; i < count; i++) { + if (fgetname(i, buf)) { + strupper(buf); + if (!lib->_imported && slib_add_external_proc(buf, lib->_id) == -1) { + break; + } else if (comp) { + char name[NAME_SIZE]; + strlcpy(name, lib->_name, sizeof(name)); + strlcat(name, ".", sizeof(name)); + strlcat(name, buf, sizeof(name)); + strupper(name); + comp_add_external_proc(name, lib->_id); + } + } + } + } + + fcount = slib_getoptptr(lib, "sblib_func_count"); + fgetname = slib_getoptptr(lib, "sblib_func_getname"); + + if (fcount && fgetname) { + int count = fcount(); + total += count; + for (int i = 0; i < count; i++) { + if (fgetname(i, buf)) { + strupper(buf); + if (!lib->_imported && slib_add_external_func(buf, lib->_id) == -1) { + break; + } else if (comp) { + char name[NAME_SIZE]; + strlcpy(name, lib->_name, sizeof(name)); + strlcat(name, ".", sizeof(name)); + strlcat(name, buf, sizeof(name)); + strupper(name); + comp_add_external_func(name, lib->_id); + } + } + } + } + + if (!total) { + log_printf("LIB: module '%s' has no exports\n", lib->_name); + } +} + +// +// build parameter table +// +static int slib_build_ptable(slib_par_t *ptable) { + int pcount = 0; + var_t *arg; + bcip_t ofs; + + if (code_peek() == kwTYPE_LEVEL_BEGIN) { + code_skipnext(); + byte ready = 0; + do { + byte code = code_peek(); + switch (code) { + case kwTYPE_EOC: + code_skipnext(); + break; + case kwTYPE_SEP: + code_skipsep(); + break; + case kwTYPE_LEVEL_END: + ready = 1; + break; + case kwTYPE_VAR: + // variable + ofs = prog_ip; + if (code_isvar()) { + // push parameter + ptable[pcount].var_p = code_getvarptr(); + ptable[pcount].byref = 1; + pcount++; + break; + } + + // restore IP + prog_ip = ofs; + // no 'break' here + default: + // default --- expression (BYVAL ONLY) + arg = v_new(); + eval(arg); + if (!prog_error) { + // push parameter + ptable[pcount].var_p = arg; + ptable[pcount].byref = 0; + pcount++; + } else { + v_free(arg); + v_detach(arg); + return pcount; + } + } + if (pcount == MAX_PARAM) { + err_parm_limit(MAX_PARAM); + } + } while (!ready && !prog_error); + // kwTYPE_LEVEL_END + code_skipnext(); + } + return pcount; +} + +// +// free parameter table +// +static void slib_free_ptable(slib_par_t *ptable, int pcount) { + for (int i = 0; i < pcount; i++) { + if (ptable[i].byref == 0) { + v_free(ptable[i].var_p); + v_detach(ptable[i].var_p); + } + } +} + +// +// execute a function or procedure +// +static int slib_exec(slib_t *lib, var_t *ret, int index, int proc) { + slib_par_t *ptable; + int pcount; + if (code_peek() == kwTYPE_LEVEL_BEGIN) { + ptable = (slib_par_t *)malloc(sizeof(slib_par_t) * MAX_PARAM); + pcount = slib_build_ptable(ptable); + } else { + ptable = NULL; + pcount = 0; + } + if (prog_error) { + slib_free_ptable(ptable, pcount); + free(ptable); + return 0; + } + + int success; + v_init(ret); + if (proc) { + success = lib->_sblib_proc_exec(index, pcount, ptable, ret); + } else { + success = lib->_sblib_func_exec(index, pcount, ptable, ret); + } + + // error + if (!success) { + if (ret->type == V_STR) { + err_throw("LIB:%s: %s\n", lib->_name, ret->v.p.ptr); + } else { + err_throw("LIB:%s: Unspecified error calling %s\n", lib->_name, (proc ? "SUB" : "FUNC")); + } + } + + // clean-up + if (ptable) { + slib_free_ptable(ptable, pcount); + free(ptable); + } + + return success; +} + +void plugin_init() { + for (int i = 0; i < MAX_SLIBS; i++) { + plugins[i] = NULL; + } +} + +int plugin_import(const char *name, const char *alias) { + int result = -1; + char path[PATH_SIZE]; + char file[PATH_SIZE]; + + slib_init_path(name, path, file); + + if (slib_find_path(path, file)) { + for (int i = 0; i < MAX_SLIBS; i++) { + if (!plugins[i]) { + // found free slot + slib_t *lib = plugins[i] = (slib_t *)calloc(sizeof(slib_t), 1); + if (!lib) { + sc_raise("LIB: plugin_import failed"); + break; + } + if (slib_open(path, file, alias, i)) { + slib_import_routines(lib, 1); + lib->_imported = 1; + } else { + sc_raise("LIB: plugin_import failed"); + } + result = i; + break; + } + } + } + return result; +} + +void plugin_open(const char *name, int lib_id) { + slib_t *lib = get_lib(lib_id); + if (!lib && lib_id >= 0 && lib_id < MAX_SLIBS) { + lib = plugins[lib_id] = (slib_t *)calloc(sizeof(slib_t), 1); + if (lib) { + char path[PATH_SIZE]; + char file[PATH_SIZE]; + slib_init_path(name, path, file); + if (!slib_find_path(path, file)) { + rt_raise("LIB: can't open %s", name); + } else if (!slib_open(path, file, file, lib_id)) { + rt_raise("LIB: can't open %s", name); + } + } + } + if (lib && !prog_error) { + if (!lib->_imported) { + slib_import_routines(lib, 0); + lib->_imported = 1; + } + sblib_init_fn minit = slib_getoptptr(lib, "sblib_init"); + if (minit && !minit(gsb_last_file)) { + rt_raise("LIB: %s->sblib_init(), failed", lib->_name); + } + } else { + rt_raise("LIB: plugin_open failed"); + } +} + +int plugin_get_kid(int lib_id, const char *name) { + slib_t *lib = get_lib(lib_id); + if (lib != NULL) { + const char *dot = strchr(name, '.'); + const char *field = (dot != NULL ? dot + 1 : name); + for (int i = 0; i < lib->_proc_count; i++) { + if (lib->_proc_list[i].lib_id == lib_id && + strcmp(lib->_proc_list[i].name, field) == 0) { + return i; + } + } + for (int i = 0; i < lib->_func_count; i++) { + if (lib->_func_list[i].lib_id == lib_id && + strcmp(lib->_func_list[i].name, field) == 0) { + return i; + } + } + } + return -1; +} + +void *plugin_get_func(const char *name) { + void *result = NULL; + for (int i = 0; i < MAX_SLIBS && result == NULL; i++) { + if (plugins[i]) { + slib_t *lib = plugins[i]; + if (lib->_imported) { + result = slib_getoptptr(lib, name); + } + } + } + return result; +} + +int plugin_procexec(int lib_id, int index) { + int result; + slib_t *lib = get_lib(lib_id); + if (lib && lib->_sblib_proc_exec) { + var_t ret; + v_init(&ret); + result = slib_exec(lib, &ret, index, 1); + v_free(&ret); + } else { + result = 0; + } + return result; +} + +int plugin_funcexec(int lib_id, int index, var_t *ret) { + int result; + slib_t *lib = get_lib(lib_id); + if (lib && lib->_sblib_func_exec) { + result = slib_exec(lib, ret, index, 0); + } else { + result = 0; + } + return result; +} + +void plugin_close() { + for (int i = 0; i < MAX_SLIBS; i++) { + if (plugins[i]) { + slib_t *lib = plugins[i]; + if (lib->_handle) { + sblib_close_fn mclose = slib_getoptptr(lib, "sblib_close"); + if (mclose) { + mclose(); + } + slib_llclose(lib); + } + free(lib->_proc_list); + free(lib->_func_list); + free(lib); + } + plugins[i] = NULL; + } +} + +#else +// dummy implementations +void plugin_init() {} +int plugin_import(const char *name, const char *alias) { return -1; } +void plugin_open(const char *name, int lib_id) { } +int plugin_get_kid(int lib_id, const char *keyword) { return -1; } +void *plugin_get_func(const char *name) { return 0; } +int plugin_procexec(int lib_id, int index) { return -1; } +int plugin_funcexec(int lib_id, int index, var_t *ret) { return -1; } +void plugin_close() {} +#endif diff --git a/src/common/plugins.h b/src/common/plugins.h new file mode 100644 index 00000000..0496f5cf --- /dev/null +++ b/src/common/plugins.h @@ -0,0 +1,66 @@ +// This file is part of SmallBASIC +// +// SmallBASIC - External library support (plugins) +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// +// Copyright(C) 2001 Nicholas Christopoulos + +#if !defined(SB_PLUGINS) +#define SB_PLUGINS + +#include "common/sys.h" +#include "common/var.h" +#include "common/device.h" +#include "include/module.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +// +// initialise the plugin system +// +void plugin_init(); + +// +// locates the plugin, imports the keywords and returns the associated ID +// +int plugin_import(const char *name, const char *alias); + +// +// opens the plugin ready for execution. reuses any existing compiler data +// when not invoked from an sbx. otherwise creates the name:id association. +// +void plugin_open(const char *name, int lib_id); + +// +// returns the keyword ID +// +int plugin_get_kid(int lib_id, const char *keyword); + +// +// returns the function pointer for the given function name +// +void *plugin_get_func(const char *name); + +// +// executes the plugin procedure at the given index +// +int plugin_procexec(int lib_id, int index); + +// +// executes the plugin function at the given index +// +int plugin_funcexec(int lib_id, int index, var_t *ret); + +// +// closes the plugin system +// +void plugin_close(); + +#if defined(__cplusplus) +} +#endif +#endif diff --git a/src/common/proc.c b/src/common/proc.c index 96344873..ec65ef2c 100644 --- a/src/common/proc.c +++ b/src/common/proc.c @@ -70,7 +70,9 @@ int sys_search_path(const char *path, const char *file, char *retbuf) { strlcpy(cur_path, getenv("HOMEDRIVE"), sizeof(cur_path)); strlcat(cur_path, getenv("HOMEPATH"), sizeof(cur_path)); } - strlcat(cur_path, "/", sizeof(cur_path)); + if (old_path[0] != '/') { + strlcat(cur_path, "/", sizeof(cur_path)); + } strlcat(cur_path, old_path, sizeof(cur_path)); free(old_path); } diff --git a/src/common/sbapp.h b/src/common/sbapp.h index 7f50d33f..a3e5586a 100644 --- a/src/common/sbapp.h +++ b/src/common/sbapp.h @@ -15,7 +15,7 @@ #include "common/kw.h" #include "common/pproc.h" #include "common/var.h" -#include "common/extlib.h" +#include "common/plugins.h" #include "common/units.h" #if defined(__cplusplus) diff --git a/src/common/scan.c b/src/common/scan.c index 9aac196e..d8385c0a 100644 --- a/src/common/scan.c +++ b/src/common/scan.c @@ -14,8 +14,8 @@ #include "common/bc.h" #include "common/scan.h" #include "common/smbas.h" +#include "common/plugins.h" #include "common/units.h" -#include "common/extlib.h" #include "common/messages.h" #include "languages/keywords.en.c" @@ -1872,10 +1872,18 @@ char *comp_array_params(char *src, char exitChar) { } if (*(p + 1) == '.') { p = comp_array_uds_field(p + 2, &comp_prog); + continue; } } } break; + case '"': + // skip past the embedded string + p++; + while (*p && *p != '"') { + p++; + } + break; }; if (*p != exitChar) { p++; @@ -4231,6 +4239,16 @@ void comp_preproc_grmode(const char *source) { * copy the unit name from the source string to the given buffer */ const char *get_unit_name(const char *p, char *buf_p) { + if (*p == '"') { + // copy the quoted string + *buf_p++ = *p++; + while (*p && *p != '"') { + *buf_p++ = *p++; + } + if (*p == '"') { + *buf_p++ = *p++; + } + } while (is_alnum(*p) || *p == '_' || *p == '.') { if (*p == '.') { *buf_p++ = OS_DIRSEP; @@ -4265,23 +4283,21 @@ const char *get_alias(const char *p, char *alias, const char *def) { void comp_preproc_import(const char *slist) { char buf[OS_PATHNAME_SIZE + 1]; char alias[OS_PATHNAME_SIZE + 1]; - const char *p = slist; SKIP_SPACES(p); - while (is_alpha(*p)) { + while (is_alpha(*p) || *p == '"') { // get name - "Import other.Foo => "other/Foo" p = get_unit_name(p, buf); p = get_alias(p, alias, buf); // import name strlower(buf); - int uid = slib_get_module_id(buf, alias); + int uid = plugin_import(buf, alias); if (uid != -1) { // store C module lib-record - slib_import(uid, 1); - add_libtable_rec(alias, alias, uid, 0); + add_libtable_rec(buf, alias, uid, 0); } else { uid = open_unit(buf, alias); if (uid < 0) { @@ -4436,12 +4452,8 @@ char *comp_preproc_options(char *p) { opt_command[OPT_CMD_SZ - 1] = '\0'; } *pe = lc; - } else if (strncmp(LCN_LOAD_MODULES, p, LEN_LDMODULES) == 0 && - opt_modpath[0] != '\0') { - if (!opt_loadmod) { - opt_loadmod = 1; - slib_init(); - } + } else if (strncmp(LCN_LOAD_MODULES, p, LEN_LDMODULES) == 0) { + // does nothing - for backwards compatibility } else { SKIP_SPACES(p); char *pe = p; diff --git a/src/common/smbas.h b/src/common/smbas.h index 8fb9b5e0..6ddce361 100644 --- a/src/common/smbas.h +++ b/src/common/smbas.h @@ -104,7 +104,6 @@ EXTERN byte opt_graphics; /**< command-line option: start in graphics mode */ EXTERN byte opt_quiet; /**< command-line option: quiet */ EXTERN char opt_command[OPT_CMD_SZ]; /**< command-line parameters (COMMAND$) */ EXTERN int opt_base; /**< OPTION BASE x */ -EXTERN byte opt_loadmod; /**< load all modules */ EXTERN char opt_modpath[OPT_MOD_SZ]; /**< Modules path */ EXTERN int opt_verbose; /**< print some additional infos */ EXTERN int opt_ide; /**< 0=no IDE, 1=IDE is linked, 2=IDE is external exe) */ diff --git a/src/common/sys.h b/src/common/sys.h index ab60ee18..b3ab8d6a 100644 --- a/src/common/sys.h +++ b/src/common/sys.h @@ -76,6 +76,8 @@ extern "C" { #define SB_STR_VER VERSION " Android " BUILD_DATE #elif defined (_FLTK) #define SB_STR_VER VERSION " FLTK " BUILD_DATE +#elif defined (_EMCC) + #define SB_STR_VER VERSION " Emscripten " BUILD_DATE #else #define SB_STR_VER VERSION " Console " SB_VERSYS SB_BIT_SZ BUILD_DATE #endif diff --git a/src/common/units.c b/src/common/units.c index 06b9c119..bd4e6bc6 100644 --- a/src/common/units.c +++ b/src/common/units.c @@ -31,9 +31,7 @@ void unit_mgr_init() { * close up */ void unit_mgr_close() { - int i; - - for (i = 0; i < unit_count; i++) { + for (int i = 0; i < unit_count; i++) { if (units[i].status == unit_loaded) { close_unit(i); } diff --git a/src/common/units.h b/src/common/units.h index 2af64a80..590968a2 100644 --- a/src/common/units.h +++ b/src/common/units.h @@ -81,7 +81,7 @@ typedef struct { unit_file_t hdr; /**< data from file */ unit_sym_t *symbols; /**< table of symbols */ -}unit_t; +} unit_t; /** * @ingroup exec diff --git a/src/common/var.c b/src/common/var.c index f23bdffa..17929539 100644 --- a/src/common/var.c +++ b/src/common/var.c @@ -718,7 +718,6 @@ void v_strcat(var_t *var, const char *str) { strcpy(var->v.p.ptr, p); strcat(var->v.p.ptr, str); } - } else { err_typemismatch(); } diff --git a/src/common/var_map.c b/src/common/var_map.c index a363a0bc..37bb263b 100644 --- a/src/common/var_map.c +++ b/src/common/var_map.c @@ -434,7 +434,13 @@ void map_set_primative(var_p_t dest, const char *s, int len) { } } if (text) { - v_setstrn(dest, s, len); + if (len == 4 && strncasecmp(s, "true", len) == 0) { + v_setint(dest, 1); + } else if (len == 5 && strncasecmp(s, "false", len) == 0) { + v_setint(dest, 0); + } else { + v_setstrn(dest, s, len); + } } else if (fract) { v_setreal(dest, atof(s)); } else { @@ -499,10 +505,10 @@ int map_create_array(var_p_t dest, JsonTokens *json, int end_position, int index break; } var_t *elem = map_array_list_add(&list, rows, curcol++); - if (token.type == JSMN_PRIMITIVE && json->tokens[0].type == JSMN_ARRAY) { - int len = token.end - token.start; - const char *str = json->js + token.start; - const char *delim = memchr(str, ';', len); + int len = token.end - token.start; + const char *str = json->js + token.start; + const char *delim = memchr(str, ';', len); + if (token.type == JSMN_PRIMITIVE && (delim != NULL || json->tokens[0].type == JSMN_ARRAY)) { if (delim != NULL) { if ((delim - str) > 0) { map_set_primative(elem, str, delim - str); @@ -538,7 +544,7 @@ int map_create_array(var_p_t dest, JsonTokens *json, int end_position, int index cols = curcol; } } - map_build_array(dest, list.head, rows+1, cols); + map_build_array(dest, list.head, rows + 1, cols); return i; } diff --git a/src/include/module.h b/src/include/module.h index a8ea43b3..1618f3e6 100644 --- a/src/include/module.h +++ b/src/include/module.h @@ -33,15 +33,6 @@ typedef struct { */ int sblib_init(const char *sourceFile); -/** - * @ingroup modlib - * - * returns the module name - * - * @return module name - */ -const char *sblib_get_module_name(); - /** * @ingroup modstd * diff --git a/src/languages/keywords.en.c b/src/languages/keywords.en.c index 07507c20..9424f8c4 100644 --- a/src/languages/keywords.en.c +++ b/src/languages/keywords.en.c @@ -269,8 +269,6 @@ struct func_keyword_s func_table[] = { { "LOG10", kwLOG10 }, { "FIX", kwFIX }, { "INT", kwINT }, -{ "CDBL", kwCDBL }, -{ "CREAL", kwCDBL }, { "DEG", kwDEG }, { "RAD", kwRAD }, { "PEN", kwPENF }, @@ -279,7 +277,6 @@ struct func_keyword_s func_table[] = { { "FRAC", kwFRAC }, { "FRE", kwFRE }, { "SGN", kwSGN }, -{ "CINT", kwCINT }, { "EOF", kwEOF }, { "SEEK", kwSEEKF }, { "LOF", kwLOF }, diff --git a/src/lib/jsmn b/src/lib/jsmn index 053d3cd2..25647e69 160000 --- a/src/lib/jsmn +++ b/src/lib/jsmn @@ -1 +1 @@ -Subproject commit 053d3cd29200edb1bfd181d917d140c16c1f8834 +Subproject commit 25647e692c7906b96ffd2b05ca54c097948e879c diff --git a/src/lib/lodepng b/src/lib/lodepng index 8c6a9e30..5601b827 160000 --- a/src/lib/lodepng +++ b/src/lib/lodepng @@ -1 +1 @@ -Subproject commit 8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a +Subproject commit 5601b8272a6850b7c5d693dd0c0e16da50be8d8d diff --git a/src/lib/maapi.h b/src/lib/maapi.h index 56a7bba0..1443a87e 100755 --- a/src/lib/maapi.h +++ b/src/lib/maapi.h @@ -324,6 +324,11 @@ void maHideVirtualKeyboard(void); */ int maGetEvent(MAEvent *event); +/** + * Push an event onto the queue + */ +void maPushEvent(MAEvent *event); + /** * Suspends execution until there is an event in the buffer, * or \a timeout milliseconds have passed. A timeout <= 0 is considered infinite. diff --git a/src/lib/miniaudio b/src/lib/miniaudio index 37fe1343..82e70f4c 160000 --- a/src/lib/miniaudio +++ b/src/lib/miniaudio @@ -1 +1 @@ -Subproject commit 37fe1343f04f6fd9bd82229ca50a48b77ecce564 +Subproject commit 82e70f4cbe6e613c8edc0ac7b97ff3dd00f2ca27 diff --git a/src/lib/stb b/src/lib/stb index 3a117406..af1a5bc3 160000 --- a/src/lib/stb +++ b/src/lib/stb @@ -1 +1 @@ -Subproject commit 3a1174060a7dd4eb652d4e6854bc4cd98c159200 +Subproject commit af1a5bc352164740c1cc1354942b1c6b72eacb8a diff --git a/src/platform/android/build.gradle b/src/platform/android/build.gradle index 4f2ce36a..24999131 100644 --- a/src/platform/android/build.gradle +++ b/src/platform/android/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.1' + classpath 'com.android.tools.build:gradle:7.0.2' } } diff --git a/src/platform/android/jni/common/Android.mk b/src/platform/android/jni/common/Android.mk index 7bf63f18..445948e8 100644 --- a/src/platform/android/jni/common/Android.mk +++ b/src/platform/android/jni/common/Android.mk @@ -33,7 +33,7 @@ LOCAL_SRC_FILES := \ $(COMMON)/system.c \ $(COMMON)/random.c \ $(COMMON)/eval.c \ - $(COMMON)/extlib.c \ + $(COMMON)/plugins.c \ $(COMMON)/file.c \ $(COMMON)/ffill.c \ $(COMMON)/fmt.c \ diff --git a/src/platform/android/jni/runtime.cpp b/src/platform/android/jni/runtime.cpp index 5ef2bb75..a7b649ee 100644 --- a/src/platform/android/jni/runtime.cpp +++ b/src/platform/android/jni/runtime.cpp @@ -475,7 +475,6 @@ void Runtime::runShell() { os_graphics = 1; os_color_depth = 16; opt_mute_audio = 0; - opt_loadmod = 0; _app->activity->callbacks->onContentRectChanged = onContentRectChanged; loadConfig(); @@ -1180,6 +1179,10 @@ int maGetEvent(MAEvent *event) { return result; } +void maPushEvent(MAEvent *maEvent) { + runtime->pushEvent(maEvent); +} + void maWait(int timeout) { runtime->pause(timeout); } @@ -1343,10 +1346,6 @@ struct LibProcs { {"SPEAK", tts_speak} }; -const char *sblib_get_module_name() { - return "android"; -} - int sblib_proc_count(void) { return (sizeof(lib_procs) / sizeof(lib_procs[0])); } diff --git a/src/platform/android/jni/runtime.h b/src/platform/android/jni/runtime.h index fab5a2d3..3e048917 100644 --- a/src/platform/android/jni/runtime.h +++ b/src/platform/android/jni/runtime.h @@ -62,7 +62,7 @@ struct Runtime : public System { char *loadResource(const char *fileName); void optionsBox(StringList *items); void saveWindowRect() {} - void setWindowSize(int width, int height) {}; + void setWindowRect(int x, int y, int width, int height) {}; void setWindowTitle(const char *title) {} void share(const char *path) { setString("share", path); } void showCursor(CursorType cursorType) {} diff --git a/src/platform/console/device.cpp b/src/platform/console/device.cpp index 28b331aa..1571567d 100644 --- a/src/platform/console/device.cpp +++ b/src/platform/console/device.cpp @@ -10,7 +10,7 @@ #include "config.h" #include "include/osd.h" #include "common/device.h" -#include "common/extlib.h" +#include "common/plugins.h" #include "common/smbas.h" #define WAIT_INTERVAL 5 @@ -90,33 +90,35 @@ void console_init() { p_write = default_write; } +// // initialize driver +// int osd_devinit() { - p_arc = (arc_fn)slib_get_func("sblib_arc"); - p_audio = (audio_fn)slib_get_func("sblib_audio"); - p_beep = (beep_fn)slib_get_func("sblib_beep"); - p_clear_sound_queue = (clear_sound_queue_fn)slib_get_func("sblib_clear_sound_queue"); - p_cls = (cls_fn)slib_get_func("sblib_cls"); - p_ellipse = (ellipse_fn)slib_get_func("sblib_ellipse"); - p_events = (events_fn)slib_get_func("sblib_events"); - p_getpen = (getpen_fn)slib_get_func("sblib_getpen"); - p_getpixel = (getpixel_fn)slib_get_func("sblib_getpixel"); - p_getx = (getx_fn)slib_get_func("sblib_getx"); - p_gety = (gety_fn)slib_get_func("sblib_gety"); - p_line = (line_fn)slib_get_func("sblib_line"); - p_rect = (rect_fn)slib_get_func("sblib_rect"); - p_refresh = (refresh_fn)slib_get_func("sblib_refresh"); - p_setcolor = (setcolor_fn)slib_get_func("sblib_setcolor"); - p_setpenmode = (setpenmode_fn)slib_get_func("sblib_setpenmode"); - p_setpixel = (setpixel_fn)slib_get_func("sblib_setpixel"); - p_settextcolor = (settextcolor_fn)slib_get_func("sblib_settextcolor"); - p_setxy = (setxy_fn)slib_get_func("sblib_setxy"); - p_sound = (sound_fn)slib_get_func("sblib_sound"); - p_textheight = (textheight_fn)slib_get_func("sblib_textheight"); - p_textwidth = (textwidth_fn)slib_get_func("sblib_textwidth"); - p_write = (write_fn)slib_get_func("sblib_write"); - - init_fn devinit = (init_fn)slib_get_func("sblib_devinit"); + p_arc = (arc_fn)plugin_get_func("sblib_arc"); + p_audio = (audio_fn)plugin_get_func("sblib_audio"); + p_beep = (beep_fn)plugin_get_func("sblib_beep"); + p_clear_sound_queue = (clear_sound_queue_fn)plugin_get_func("sblib_clear_sound_queue"); + p_cls = (cls_fn)plugin_get_func("sblib_cls"); + p_ellipse = (ellipse_fn)plugin_get_func("sblib_ellipse"); + p_events = (events_fn)plugin_get_func("sblib_events"); + p_getpen = (getpen_fn)plugin_get_func("sblib_getpen"); + p_getpixel = (getpixel_fn)plugin_get_func("sblib_getpixel"); + p_getx = (getx_fn)plugin_get_func("sblib_getx"); + p_gety = (gety_fn)plugin_get_func("sblib_gety"); + p_line = (line_fn)plugin_get_func("sblib_line"); + p_rect = (rect_fn)plugin_get_func("sblib_rect"); + p_refresh = (refresh_fn)plugin_get_func("sblib_refresh"); + p_setcolor = (setcolor_fn)plugin_get_func("sblib_setcolor"); + p_setpenmode = (setpenmode_fn)plugin_get_func("sblib_setpenmode"); + p_setpixel = (setpixel_fn)plugin_get_func("sblib_setpixel"); + p_settextcolor = (settextcolor_fn)plugin_get_func("sblib_settextcolor"); + p_setxy = (setxy_fn)plugin_get_func("sblib_setxy"); + p_sound = (sound_fn)plugin_get_func("sblib_sound"); + p_textheight = (textheight_fn)plugin_get_func("sblib_textheight"); + p_textwidth = (textwidth_fn)plugin_get_func("sblib_textwidth"); + p_write = (write_fn)plugin_get_func("sblib_write"); + + init_fn devinit = (init_fn)plugin_get_func("sblib_devinit"); if (devinit) { devinit(prog_file, opt_pref_width, opt_pref_height); } @@ -132,27 +134,35 @@ int osd_devinit() { return 1; } +// // close driver +// int osd_devrestore() { return 1; } +// // set foreground and background color // a value of -1 means not change that color +// void osd_settextcolor(long fg, long bg) { if (p_settextcolor) { p_settextcolor(fg, bg); } } +// // enable or disable PEN/MOUSE driver +// void osd_setpenmode(int enable) { if (p_setpenmode) { p_setpenmode(enable); } } +// // return pen/mouse info ('code' is the rq, see doc) +// int osd_getpen(int code) { int result; if (p_getpen) { @@ -163,14 +173,18 @@ int osd_getpen(int code) { return result; } +// // clear screen +// void osd_cls() { if (p_cls) { p_cls(); } } +// // returns the current x position (text-mode cursor) +// int osd_getx() { int result; if (p_getx) { @@ -181,7 +195,9 @@ int osd_getx() { return result; } +// // returns the current y position (text-mode cursor) +// int osd_gety() { int result; if (p_gety) { @@ -192,19 +208,25 @@ int osd_gety() { return result; } +// // set's text-mode (or graphics) cursor position +// void osd_setxy(int x, int y) { if (p_setxy) { p_setxy(x, y); } } +// // Basic output - print sans control codes +// void osd_write(const char *str) { p_write(str); } +// // events loop (called from main, every 50ms) +// int osd_events(int wait_flag) { int result = 0; if (p_events) { @@ -218,42 +240,54 @@ int osd_events(int wait_flag) { return result; } +// // sets foreground color +// void osd_setcolor(long color) { if (p_setcolor) { p_setcolor(color); } } +// // draw a line +// void osd_line(int x1, int y1, int x2, int y2) { if (p_line) { p_line(x1, y1, x2, y2); } } +// // draw an ellipse +// void osd_ellipse(int xc, int yc, int xr, int yr, int fill) { if (p_ellipse) { p_ellipse(xc, yc, xr, yr, fill); } } +// // draw an arc +// void osd_arc(int xc, int yc, double r, double as, double ae, double aspect) { if (p_arc) { p_arc(xc, yc, r, as, ae, aspect); } } +// // draw a pixel +// void osd_setpixel(int x, int y) { if (p_setpixel) { p_setpixel(x, y); } } +// // returns pixel's color +// long osd_getpixel(int x, int y) { long result; if (p_getpixel) { @@ -264,21 +298,27 @@ long osd_getpixel(int x, int y) { return result; } +// // draw rectangle (parallelogram) +// void osd_rect(int x1, int y1, int x2, int y2, int fill) { if (p_rect) { p_rect(x1, y1, x2, y2, fill); } } +// // refresh/flush the screen/stdout +// void osd_refresh() { if (p_refresh) { p_refresh(); } } +// // just a beep +// void osd_beep() { if (p_beep) { p_beep(); @@ -287,28 +327,36 @@ void osd_beep() { } } +// // play a sound +// void osd_sound(int frq, int ms, int vol, int bgplay) { if (p_sound) { p_sound(frq, ms, vol, bgplay); } } +// // clears sound-queue (stop background sound) +// void osd_clear_sound_queue() { if (p_clear_sound_queue) { p_clear_sound_queue(); } } +// // play the given audio file +// void osd_audio(const char *path) { if (p_audio) { p_audio(path); } } +// // text-width in pixels +// int osd_textwidth(const char *str) { int result; if (p_textwidth) { @@ -319,7 +367,9 @@ int osd_textwidth(const char *str) { return result; } +// // text-height in pixels +// int osd_textheight(const char *str) { int result; if (p_textheight) { @@ -330,7 +380,9 @@ int osd_textheight(const char *str) { return result; } +// // delay while pumping events +// void dev_delay(uint32_t timeout) { uint32_t slept = 0; uint32_t now = dev_get_millisecond_count(); @@ -349,7 +401,9 @@ void dev_delay(uint32_t timeout) { } } +// // unused +// void dev_log_stack(const char *keyword, int type, int line) {} void v_create_form(var_p_t var) {} void v_create_window(var_p_t var) {} diff --git a/src/platform/console/image.cpp b/src/platform/console/image.cpp index 04b17965..67c172a1 100644 --- a/src/platform/console/image.cpp +++ b/src/platform/console/image.cpp @@ -375,11 +375,12 @@ void cmd_image_save(var_s *self, var_s *) { dev_file_t *filep = nullptr; byte code = code_peek(); int error = -1; - int w = image->_width; - int h = image->_height; + var_t *array; var_t var; if (!prog_error && image != nullptr) { + unsigned w = image->_width; + unsigned h = image->_height; switch (code) { case kwTYPE_SEP: filep = get_file(); @@ -387,6 +388,24 @@ void cmd_image_save(var_s *self, var_s *) { error = lodepng_encode32_file(filep->name, image->_image, w, h); } break; + case kwTYPE_VAR: + array = par_getvar_ptr(); + v_tomatrix(array, h, w); + // x0 x1 x2 (w=3,h=2) + // y0 rgba rgba rgba ypos=0 + // y1 rgba rgba rgba ypos=12 + // + for (unsigned y = 0; y < h; y++) { + unsigned yoffs = (y * w * 4); + for (unsigned x = 0; x < w; x++) { + uint8_t a, r, g, b; + GET_IMAGE_ARGB(image->_image, yoffs + (x * 4), a, r, g, b); + pixel_t px = v_get_argb_px(a, r, g, b); + unsigned pos = y * w + x; + v_setint(v_elem(array, pos), px); + } + } + break; default: v_init(&var); eval(&var); diff --git a/src/platform/console/input.cpp b/src/platform/console/input.cpp index 87a5efa2..3772af81 100644 --- a/src/platform/console/input.cpp +++ b/src/platform/console/input.cpp @@ -209,6 +209,5 @@ char *dev_gets(char *dest, int size) { } } while (ch != '\n' && ch != '\r'); dest[len] = '\0'; - dev_print(dest); return dest; } diff --git a/src/platform/console/main.cpp b/src/platform/console/main.cpp index c6d23e9b..1f1e8f9a 100644 --- a/src/platform/console/main.cpp +++ b/src/platform/console/main.cpp @@ -28,6 +28,7 @@ static struct option OPTIONS[] = { {"keywords", no_argument, NULL, 'k'}, {"no-file-access", no_argument, NULL, 'f'}, {"gen-sbx", no_argument, NULL, 'x'}, + {"live-mode", no_argument, NULL, 'i'}, {"module-path", optional_argument, NULL, 'm'}, {"decompile", optional_argument, NULL, 's'}, {"option", optional_argument, NULL, 'o'}, @@ -92,9 +93,9 @@ void command_help(const char *selection) { } } -/* - * handles the command "sbasic -pkw" - */ +// +// handles the command "sbasic -pkw" +// void print_keywords() { printf("SmallBASIC keywords table\n"); printf("::':#:rem:\"\n"); // ted's format @@ -138,9 +139,9 @@ void print_keywords() { } } -/* - * setup to run a program passed from the command line - */ +// +// setup to run a program passed from the command line +// bool setup_command_program(const char *program, char **runFile) { char file[OS_PATHNAME_SIZE + 1]; @@ -218,7 +219,7 @@ void decompile(const char *path) { opt_nosave = 1; init_tasks(); unit_mgr_init(); - slib_init(); + plugin_init(); if (sbasic_compile(path)) { int exec_tid = sbasic_exec_prepare(path); @@ -229,19 +230,19 @@ void decompile(const char *path) { // cleanup unit_mgr_close(); - slib_close(); + plugin_close(); destroy_tasks(); chdir(prev_cwd); } -/* - * process command-line parameters - */ -bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile) { +// +// process command-line parameters +// +bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile, bool *iterate) { bool result = true; while (result) { int option_index = 0; - int c = getopt_long(argc, argv, "vkfxm:s:o:c:h::", OPTIONS, &option_index); + int c = getopt_long(argc, argv, "vkfxim:s:o:c:h::", OPTIONS, &option_index); if (c == -1 && !option_index) { // no more options for (int i = 1; i < argc; i++) { @@ -286,7 +287,6 @@ bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile) { opt_nosave = 0; break; case 'm': - opt_loadmod = 1; if (optarg) { strcpy(opt_modpath, optarg); } @@ -310,6 +310,9 @@ bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile) { result = false; } break; + case 'i': + *iterate = true; + break; default: show_help(); result = false; @@ -317,10 +320,6 @@ bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile) { } } - if (getenv("SBASICPATH") != nullptr) { - opt_loadmod = 1; - } - if (strcmp("--", argv[argc - 1]) == 0) { if (*runFile != nullptr) { // run file already set @@ -361,16 +360,53 @@ bool process_options(int argc, char *argv[], char **runFile, bool *tmpFile) { return result; } -/* - * program entry point - */ +uint32_t get_modified_time(const char *file) { + uint32_t result = 0; + if (file != nullptr) { + struct stat st_file; + if (!stat(file, &st_file)) { + result = st_file.st_mtime; + } + } + return result; +} + +#if !defined(__MACH__) && !defined(_Win32) +bool wait_for_file(const char *file, uint32_t modifiedTime) { + bool result = false; + fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); + fprintf(stdout, "Fix the error [in %s] to continue, or press enter to exit...\n", file); + while (!result && modifiedTime == get_modified_time(file)) { + char c = 0; + read(0, &c, 1); + if (c != 0) { + result = true; + break; + } + usleep(500 * 1000); + } + return result; +} +#else +bool wait_for_file(const char *file, uint32_t modifiedTime) { + bool result = false; + fprintf(stdout, "Fix the error [in %s] to continue...\n", file); + while (!result && modifiedTime == get_modified_time(file)) { + usleep(500 * 1000); + } + return result; +} +#endif + +// +// program entry point +// int main(int argc, char *argv[]) { opt_autolocal = 0; opt_command[0] = '\0'; opt_modpath[0] = '\0'; opt_file_permitted = 1; opt_ide = 0; - opt_loadmod = 0; opt_nosave = 1; opt_pref_height = 0; opt_pref_width = 0; @@ -384,11 +420,20 @@ int main(int argc, char *argv[]) { char *file = nullptr; bool tmpFile = false; - if (process_options(argc, argv, &file, &tmpFile)) { + bool iterate = false; + if (process_options(argc, argv, &file, &tmpFile, &iterate)) { char prev_cwd[OS_PATHNAME_SIZE + 1]; prev_cwd[0] = 0; getcwd(prev_cwd, sizeof(prev_cwd) - 1); - sbasic_main(file); + do { + uint32_t modifiedTime = get_modified_time(file); + bool result = sbasic_main(file); + if (!result && iterate) { + if (wait_for_file(file, modifiedTime)) { + break; + } + } + } while (iterate); chdir(prev_cwd); if (tmpFile) { unlink(file); diff --git a/src/platform/emcc/Makefile.am b/src/platform/emcc/Makefile.am new file mode 100644 index 00000000..b412230e --- /dev/null +++ b/src/platform/emcc/Makefile.am @@ -0,0 +1,49 @@ +# SmallBASIC command line version +# Copyright(C) 2001-2022 Chris Warren-Smith. +# +# This program is distributed under the terms of the GPL v2.0 or later +# Download the GNU Public License (GPL) from www.gnu.org +# + +# +# Setup: +# +# $ git clone https://github.com/emscripten-core/emsdk.git +# $ emsdk update +# $ emsdk install latest +# $ emsdk activate latest +# $ . ./emsdk_env.sh +# +# Build: +# +# $ emconfigure ./configure --enable-emcc +# $ emmake make +# + +AM_CPPFLAGS = -I$(top_builddir)/src -I. @PACKAGE_CFLAGS@ + +CLEANFILES = sbasic.js sbasic.wasm + +bin_PROGRAMS = sbasic.html + +sbasic_html_SOURCES = \ + ../../lib/lodepng/lodepng.cpp ../../lib/lodepng/lodepng.h \ + ../../ui/ansiwidget.cpp \ + ../../ui/window.cpp \ + ../../ui/screen.cpp \ + ../../ui/system.cpp \ + ../../ui/form.cpp \ + ../../ui/inputs.cpp \ + ../../ui/textedit.cpp \ + ../../ui/image.cpp \ + ../../ui/strlib.cpp \ + ../../ui/audio.cpp \ + runtime.h runtime.cpp \ + canvas.h canvas.cpp \ + main.cpp + +sbasic_html_LDFLAGS = -O3 -lembind --shell-file shell.html -s ASYNCIFY=1 -s ALLOW_MEMORY_GROWTH=1 --preload-file basic + +sbasic_html_LDADD = -L$(top_srcdir)/src/common -lsb_common @PACKAGE_LIBS@ + +sbasic_html_DEPENDENCIES = $(top_srcdir)/src/common/libsb_common.a diff --git a/src/platform/emcc/canvas.cpp b/src/platform/emcc/canvas.cpp new file mode 100644 index 00000000..a7ee58cb --- /dev/null +++ b/src/platform/emcc/canvas.cpp @@ -0,0 +1,399 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include "config.h" +#include +#include +#include "ui/utils.h" +#include "ui/rgb.h" +#include "ui/strlib.h" +#include "lib/maapi.h" +#include "platform/emcc/canvas.h" + +static int nextId = 1000; +pixel_t drawColor; +Canvas *screen; +Canvas *drawTarget; +Font *font; + +const char *colors[] = { + "#000", // 0 black + "#000080", // 1 blue + "#008000", // 2 green + "#008080", // 3 cyan + "#800000", // 4 red + "#800080", // 5 magenta + "#800800", // 6 yellow + "#c0c0c0", // 7 white + "#808080", // 8 gray + "#0000ff", // 9 light blue + "#00ff00", // 10 light green + "#00ffff", // 11 light cyan + "#ff0000", // 12 light red + "#ff00ff", // 13 light magenta + "#ffff00", // 14 light yellow + "#fff" // 15 bright white +}; + +const uint32_t colors_i[] = { + 0x000000, // 0 black + 0x000080, // 1 blue + 0x008000, // 2 green + 0x008080, // 3 cyan + 0x800000, // 4 red + 0x800080, // 5 magenta + 0x808000, // 6 yellow + 0xC0C0C0, // 7 white + 0x808080, // 8 gray + 0x0000FF, // 9 light blue + 0x00FF00, // 10 light green + 0x00FFFF, // 11 light cyan + 0xFF0000, // 12 light red + 0xFF00FF, // 13 light magenta + 0xFFFF00, // 14 light yellow + 0xFFFFFF // 15 bright white +}; + +EM_JS(int, get_screen_height, (), { + return window.innerHeight; + }); + +EM_JS(int, get_screen_width, (), { + return window.innerWidth; + }); + +EM_JS(int, get_text_size, (int id, const char *str, const char *face), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.font = UTF8ToString(face); + + var s = UTF8ToString(str); + var metrics = ctx.measureText(s); + var height = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + 4; + var width = metrics.width; + return (Math.round(width) << 16) + height; + }); + +EM_JS(void, draw_arc, (int id, int xc, int yc, double r, double start, double end, double aspect, const char *color), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.beginPath(); + ctx.arc(xc, yc, r, start, end, false); + ctx.strokeStyle = UTF8ToString(color); + ctx.stroke(); + }); + +EM_JS(void, draw_ellipse, (int id, int xc, int yc, int rx, int ry, int fill, const char *color), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.beginPath(); + ctx.ellipse(xc, yc, rx, ry, Math.PI * 2, 0, Math.PI * 2, false); + if (fill) { + ctx.fillStyle = UTF8ToString(color); + ctx.fill(); + } else { + ctx.strokeStyle = UTF8ToString(color); + ctx.stroke(); + } + }); + +EM_JS(void, draw_line, (int id, int x1, int y1, int x2, int y2, const char *color), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.beginPath(); + ctx.moveTo(x1, y1 + 1); + ctx.lineTo(x2, y2 + 1); + ctx.lineWidth = 1; + ctx.strokeStyle = UTF8ToString(color); + ctx.stroke(); + }); + +EM_JS(void, draw_pixel, (int id, int x, int y, int r, int g, int b), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + var pixel = new Uint8ClampedArray(4); + pixel[0] = r; + pixel[1] = g; + pixel[2] = b; + pixel[3] = 255; + var imageData = new ImageData(pixel, 1, 1); + ctx.putImageData(imageData, x, y); + }); + +EM_JS(void, draw_rect_filled, (int id, int x, int y, int w, int h, const char *color), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = UTF8ToString(color); + ctx.fillRect(x, y, w, h); + }); + +EM_JS(void, draw_region, (int id, int srcId, int x, int y, int w, int h, int dx, int dy), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var destination = canvas.getContext("2d"); + var source = document.getElementById(srcId == -1 ? "canvas" : "canvas_" + srcId); + destination.drawImage(source, x, y, w, h, dx, dy, w, h); + }); + +EM_JS(void, draw_text, (int id, int x, int y, const char *str, int len, const char *color, const char *face), { + var canvas = document.getElementById(id == -1 ? "canvas" : "canvas_" + id); + var ctx = canvas.getContext("2d"); + ctx.font = UTF8ToString(face); + + var s = UTF8ToString(str).substring(0, len); + var metrics = ctx.measureText(s); + ctx.fillStyle = UTF8ToString(color); + ctx.fillText(s, x, y + metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent - 1); + }); + +strlib::String get_color() { + strlib::String result; + long c = drawColor; + if (c < 0) { + c = -c; + } + if (c >= 0 && c <= 15) { + result.append(colors[c]); + } else { + uint8_t sR, sG, sB; + GET_RGB(c, sR, sG, sB); + char buf[8]; + sprintf(buf, "#%02x%02x%02x", sR, sG, sB); + result.append(buf); + } + return result; +} + +Font::Font(int size, bool bold, bool italic) : + _size(size), + _bold(bold), + _italic(italic) { + _face.empty(); + if (italic) { + _face.append("italic "); + } + if (bold) { + _face.append("bold "); + } + _face.append(size).append("px Inconsolata, monospace"); +} + +Font::~Font() { + if (this == font) { + font = nullptr; + } +} + +Canvas::Canvas() : + _clip(nullptr), + _id(-1), + _w(0), + _h(0) { +} + +Canvas::Canvas(int width, int height) : + _clip(nullptr), + _id(-1), + _w(width), + _h(height) { +} + +Canvas::~Canvas() { + if (_id != -1) { + EM_ASM_({ + var element = document.getElementById($0 == -1 ? "canvas" : "canvas_" + $0); + document.body.removeChild(element); + }, _id); + } + if (this == screen) { + screen = nullptr; + } else if (this == drawTarget) { + drawTarget = nullptr; + } +} + +bool Canvas::create(int width, int height) { + _id = ++nextId; + _w = width; + _h = height; + EM_ASM_({ + var canvas = document.createElement("canvas"); + canvas.id = "canvas_" + $0; + canvas.width = $1; + canvas.height = $2; + canvas.style.zIndex = 1; + canvas.style.display = "none"; + canvas.style.position = "absolute"; + canvas.className = "emscripten"; + document.body.appendChild(canvas); + }, _id, width, height); + return true; +}; + +void Canvas::setClip(int x, int y, int w, int h) { + delete _clip; + if (x != 0 || y != 0 || _w != w || _h != h) { + _clip = new MARect(); + _clip->left = x; + _clip->top = y; + _clip->width = x + w; + _clip->height = y + h; + } else { + _clip = nullptr; + } +} + +// +// maapi implementation +// +MAHandle maCreatePlaceholder(void) { + MAHandle maHandle = (MAHandle) new Canvas(); + return maHandle; +} + +int maSetColor(int c) { + drawColor = GET_FROM_RGB888(c); + return c; +} + +void maSetClipRect(int left, int top, int width, int height) { + if (drawTarget) { + drawTarget->setClip(left, top, width, height); + } +} + +MAExtent maGetTextSize(const char *str) { + MAExtent result; + if (str && str[0]) { + result = (MAExtent)get_text_size(drawTarget ? drawTarget->_id : -1, str, font->_face.c_str()); + } else { + result = 0; + } + return result; +} + +MAExtent maGetScrSize(void) { + int w = get_screen_width(); + int h = get_screen_height(); + if (screen == nullptr) { + screen = new Canvas(w, h); + } + return (MAExtent)((w << 16) + h); +} + +void maDestroyPlaceholder(MAHandle maHandle) { + logEntered(); + Canvas *holder = (Canvas *)maHandle; + delete holder; +} + +MAHandle maSetDrawTarget(MAHandle maHandle) { + logEntered(); + MAHandle result = (MAHandle) drawTarget; + if (maHandle == (MAHandle) HANDLE_SCREEN || + maHandle == (MAHandle) HANDLE_SCREEN_BUFFER) { + drawTarget = screen; + } else { + drawTarget = (Canvas *)maHandle; + } + delete drawTarget->_clip; + drawTarget->_clip = nullptr; + return result; +} + +int maCreateDrawableImage(MAHandle maHandle, int width, int height) { + Canvas *drawable = (Canvas *)maHandle; + return drawable->create(width, height) ? RES_OK : -1; +} + +// +// drawing +// +void maArc(int xc, int yc, double r, double start, double end, double aspect) { + if (drawTarget) { + draw_arc(drawTarget->_id, xc, yc, r, start, end, aspect, get_color()); + } +} + +void maEllipse(int xc, int yc, int rx, int ry, int fill) { + if (drawTarget) { + draw_ellipse(drawTarget->_id, xc, yc, rx, ry, fill, get_color()); + } +} + +void maLine(int startX, int startY, int endX, int endY) { + if (drawTarget) { + draw_line(drawTarget->_id, startX, startY, endX, endY, get_color()); + } +} + +void maPlot(int posX, int posY) { + if (drawTarget) { + int c = drawColor; + if (c < 0) { + c = -c; + } + if (c >= 0 && c <= 15) { + c = colors_i[c]; + } + uint8_t sR, sG, sB; + GET_RGB(c, sR, sG, sB); + draw_pixel(drawTarget->_id, posX, posY, sR, sG, sB); + } +} + +void maFillRect(int left, int top, int width, int height) { + logEntered(); + if (drawTarget) { + draw_rect_filled(drawTarget->_id, left, top, width, height, get_color()); + } +} + +void maDrawText(int left, int top, const char *str, int length) { + if (str && str[0] && drawTarget) { + draw_text(drawTarget->_id, left, top, str, length, get_color(), font->_face.c_str()); + } +} + +void maDrawImageRegion(MAHandle maHandle, const MARect *srcRect, const MAPoint2d *dstPoint, int transformMode) { + logEntered(); + Canvas *src = (Canvas *)maHandle; + if (drawTarget && drawTarget != src) { + draw_region(drawTarget->_id, src->_id, srcRect->left, srcRect->top, srcRect->width, srcRect->height, dstPoint->x, dstPoint->y); + } +} + +void maDrawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int stride) { + appLog("maDrawRGB not yet implemented"); +} + +void maGetImageData(MAHandle maHandle, void *dst, const MARect *srcRect, int stride) { + appLog("maGetImageData not yet implemented"); +} + +// +// font +// +MAHandle maFontLoadDefault(int type, int style, int size) { + bool italic = (style & FONT_STYLE_ITALIC); + bool bold = (style & FONT_STYLE_BOLD); + Font *newFont = new Font(size, bold, italic); + return (MAHandle)newFont; +} + +MAHandle maFontSetCurrent(MAHandle maHandle) { + font = ((Font *)maHandle); + return maHandle; +} + +int maFontDelete(MAHandle maHandle) { + if (maHandle != -1) { + Font *handleFont = (Font *)maHandle; + delete handleFont; + } + return RES_FONT_OK; +} diff --git a/src/platform/emcc/canvas.h b/src/platform/emcc/canvas.h new file mode 100644 index 00000000..e1aa56bf --- /dev/null +++ b/src/platform/emcc/canvas.h @@ -0,0 +1,34 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#pragma once + +struct Canvas { + Canvas(); + Canvas(int width, int height); + virtual ~Canvas(); + + bool create(int width, int height); + void setClip(int x, int y, int w, int h); + + MARect *_clip; + int _id; + int _w; + int _h; +}; + +struct Font { + Font(int size, bool bold, bool italic); + virtual ~Font(); + + int _size; + bool _bold; + bool _italic; + + strlib::String _face; +}; diff --git a/src/platform/emcc/keymap.h b/src/platform/emcc/keymap.h new file mode 100644 index 00000000..dbd437ec --- /dev/null +++ b/src/platform/emcc/keymap.h @@ -0,0 +1,97 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#pragma once + +const int KEYMAP[][2] = { + {DOM_VK_RETURN, SB_KEY_ENTER}, + {DOM_VK_ESCAPE, SB_KEY_ESCAPE}, + {DOM_VK_ENTER, SB_KEY_ENTER}, + {DOM_VK_TAB, SB_KEY_TAB}, + {DOM_VK_BACK_SPACE, SB_KEY_BACKSPACE}, + {DOM_VK_DELETE, SB_KEY_DELETE}, + {DOM_VK_UP, SB_KEY_UP}, + {DOM_VK_DOWN, SB_KEY_DN}, + {DOM_VK_LEFT, SB_KEY_LEFT}, + {DOM_VK_RIGHT, SB_KEY_RIGHT}, + {DOM_VK_PAGE_UP, SB_KEY_PGUP}, + {DOM_VK_PAGE_DOWN, SB_KEY_PGDN}, + {DOM_VK_HOME, SB_KEY_HOME}, + {DOM_VK_END, SB_KEY_END}, + {DOM_VK_INSERT, SB_KEY_INSERT}, + {DOM_VK_F1, SB_KEY_F(1)}, + {DOM_VK_F2, SB_KEY_F(2)}, + {DOM_VK_F3, SB_KEY_F(3)}, + {DOM_VK_F4, SB_KEY_F(4)}, + {DOM_VK_F5, SB_KEY_F(5)}, + {DOM_VK_F6, SB_KEY_F(6)}, + {DOM_VK_F7, SB_KEY_F(7)}, + {DOM_VK_F8, SB_KEY_F(8)}, + {DOM_VK_F9, SB_KEY_F(9)}, + {DOM_VK_F10, SB_KEY_F(10)}, + {DOM_VK_F11, SB_KEY_F(11)}, + {DOM_VK_F12, SB_KEY_F(12)}, + {DOM_VK_CIRCUMFLEX, '^'}, + {DOM_VK_EXCLAMATION, '!'}, + {DOM_VK_DOUBLE_QUOTE, '"'}, + {DOM_VK_HASH, '#'}, + {DOM_VK_DOLLAR, '$'}, + {DOM_VK_PERCENT, '%'}, + {DOM_VK_AMPERSAND, '&'}, + {DOM_VK_UNDERSCORE, '_'}, + {DOM_VK_OPEN_PAREN, '('}, + {DOM_VK_CLOSE_PAREN, ')'}, + {DOM_VK_ASTERISK, '*'}, + {DOM_VK_PLUS, '+'}, + {DOM_VK_PIPE, '|'}, + {DOM_VK_HYPHEN_MINUS, '-'}, + {DOM_VK_OPEN_CURLY_BRACKET, '{'}, + {DOM_VK_CLOSE_CURLY_BRACKET, '}'}, + {DOM_VK_TILDE, '~'}, + {DOM_VK_COMMA, ','}, + {DOM_VK_PERIOD, '.'}, + {DOM_VK_SLASH, '/'}, + {DOM_VK_BACK_QUOTE, '`'}, + {DOM_VK_OPEN_BRACKET, '['}, + {DOM_VK_BACK_SLASH, '\\'}, + {DOM_VK_CLOSE_BRACKET, ']'}, + {DOM_VK_QUOTE, '\''}, + {0xBD, '-'}, + {0xBB, '='}, + {0xBA, ';'}, +}; + +const int SHIFT_KEYMAP[][2] = { + {DOM_VK_0, ')'}, + {DOM_VK_1, '!'}, + {DOM_VK_2, '@'}, + {DOM_VK_3, '#'}, + {DOM_VK_4, '$'}, + {DOM_VK_5, '%'}, + {DOM_VK_6, '^'}, + {DOM_VK_7, '&'}, + {DOM_VK_8, '*'}, + {DOM_VK_9, '('}, + {DOM_VK_COLON, ':'}, + {DOM_VK_EQUALS, '+'}, + {'`', '~'}, + {'-', '_'}, + {'=', '+'}, + {'[', '{'}, + {']', '}'}, + {'\\', '|'}, + {'\'', '"'}, + {';', ':'}, + {',', '<'}, + {'.', '>'}, + {'/', '?'}, +}; + +const int SHIFT_KEYMAP_LEN = sizeof(SHIFT_KEYMAP) / sizeof(SHIFT_KEYMAP[0]); +const int KEYMAP_LEN = sizeof(KEYMAP) / sizeof(KEYMAP[0]); + diff --git a/src/platform/emcc/main.cpp b/src/platform/emcc/main.cpp new file mode 100644 index 00000000..285ca424 --- /dev/null +++ b/src/platform/emcc/main.cpp @@ -0,0 +1,69 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include "config.h" + +#include "include/osd.h" +#include "common/sbapp.h" +#include "common/device.h" +#include "platform/emcc/runtime.h" + +void appLog(const char *format, ...) { + va_list args; + va_start(args, format); + unsigned size = vsnprintf(NULL, 0, format, args); + va_end(args); + + if (size) { + char *buf = (char *)malloc(size + 3); + buf[0] = '\0'; + va_start(args, format); + vsnprintf(buf, size + 1, format, args); + va_end(args); + buf[size] = '\0'; + + int i = size - 1; + while (i >= 0 && isspace(buf[i])) { + buf[i--] = '\0'; + } + strcat(buf, "\r\n"); + fputs(buf, stderr); + free(buf); + } +} + +void init() { + opt_command[0] = '\0'; + opt_file_permitted = 0; + opt_graphics = 1; + opt_ide = IDE_INTERNAL; + opt_modpath[0] = '\0'; + opt_nosave = 1; + opt_pref_height = 0; + opt_pref_width = 0; + opt_quiet = 1; + opt_verbose = 0; + opt_autolocal = 0; + os_graf_mx = 1024; + os_graf_my = 768; + os_graphics = 1; + os_color_depth = 16; + opt_mute_audio = 0; +} + +int main(int argc, char* argv[]) { + logEntered(); + + init(); + Runtime *runtime = new Runtime(); + runtime->runShell(); + delete runtime; + + logLeaving(); + return 0; +} diff --git a/src/platform/emcc/runtime.cpp b/src/platform/emcc/runtime.cpp new file mode 100644 index 00000000..689a65aa --- /dev/null +++ b/src/platform/emcc/runtime.cpp @@ -0,0 +1,651 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include "config.h" + +#include +#include +#include "include/osd.h" +#include "common/inet.h" +#include "common/device.h" +#include "lib/maapi.h" +#include "ui/audio.h" +#include "ui/theme.h" +#include "ui/utils.h" +#include "platform/emcc/runtime.h" +#include "platform/emcc/main_bas.h" +#include "platform/emcc/keymap.h" + +#define FONT_SIZE 14 +#define MAIN_BAS "__main_bas__" +#define WAIT_INTERVAL 10 +#define EVENT_TYPE_RESTART 101 +#define EVENT_TYPE_SHOW_MENU 102 +#define EVENT_TYPE_RESIZE 103 +#define EVENT_TYPE_BACK 104 +#define EVENT_TYPE_PGUP 105 +#define EVENT_TYPE_PGDN 106 +#define EVENT_TYPE_UP 107 +#define EVENT_TYPE_DN 108 + +Runtime *runtime; +String clipboard; + +int getFontSize() { + return EM_ASM_INT({ + const parameters = new URLSearchParams(window.location.search); + const result = parameters.get('fontSize') || $0; + console.log(result); + return result; + }, FONT_SIZE); +} + +MAEvent *getMotionEvent(int type, const EmscriptenMouseEvent *event) { + MAEvent *result = new MAEvent(); + result->type = type; + if (event) { + result->point.x = event->clientX; + result->point.y = event->clientY; + } + return result; +} + +MAEvent *getKeyPressedEvent(int keycode, int nativeKey = 0) { + MAEvent *result = new MAEvent(); + result->type = EVENT_TYPE_KEY_PRESSED; + result->key = keycode; + result->nativeKey = nativeKey; + return result; +} + +EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) { + return runtime->handleMouse(eventType, e); +} + +EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { + return runtime->handleKeyboard(eventType, e); +} + +EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *e, void *userData) { + MAEvent *event = new MAEvent(); + event->type = EVENT_TYPE_RESIZE; + event->point.x = e->documentBodyClientWidth; + event->point.y = e->documentBodyClientHeight; + runtime->pushEvent(event); + return 0; +} + +Runtime::Runtime() : + System() { + logEntered(); + runtime = this; + + // note: cannot call emscripten_sleep from inside the callbacks + emscripten_set_mousedown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, true, mouse_callback); + emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, true, mouse_callback); + emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, true, mouse_callback); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, true, key_callback); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, true, resize_callback); + + MAExtent screenSize = maGetScrSize(); + _output = new AnsiWidget(EXTENT_X(screenSize), EXTENT_Y(screenSize)); + _output->construct(); + _output->setTextColor(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND); + _output->setFontSize(::getFontSize()); + _eventQueue = new Stack(); + _state = kActiveState; + g_themeId = 0; +} + +Runtime::~Runtime() { + logEntered(); + emscripten_html5_remove_all_event_listeners(); + delete _output; + delete _eventQueue; + runtime = nullptr; + _output = nullptr; + _eventQueue = nullptr; +} + +void Runtime::alert(const char *title, const char *message) { + EM_ASM_({ + window.alert(UTF8ToString($0) + "\n" + UTF8ToString($1)); + }, title, message); +} + +int Runtime::ask(const char *title, const char *prompt, bool cancel) { + return EM_ASM_INT({ + return !window.confirm(UTF8ToString($0) + "\n" + UTF8ToString($1)); + }, title, prompt); +} + +void Runtime::browseFile(const char *url) { + EM_ASM_({ + window.open(UTF8ToString($0)); + }, url); +} + +char *Runtime::getClipboardText() { + return strdup(clipboard.c_str()); +} + +char *Runtime::loadResource(const char *fileName) { + logEntered(); + char *buffer = System::loadResource(fileName); + if (buffer == nullptr && strcmp(fileName, MAIN_BAS) == 0) { + buffer = (char *)malloc(main_bas_len + 1); + memcpy(buffer, main_bas, main_bas_len); + buffer[main_bas_len] = '\0'; + } + return buffer; +} + +bool Runtime::handleKeyboard(int eventType, const EmscriptenKeyboardEvent *e) { + int keyCode = e->keyCode; + bool result = true; + static bool capsLock = false; + switch (e->keyCode) { + case DOM_VK_SHIFT: + case DOM_VK_CONTROL: + case DOM_VK_ALT: + // ignore press without modifier key + result = false; + break; + + case DOM_VK_F5: + case DOM_VK_F11: + case DOM_VK_F12: + // ignore standard browser keystrokes + result = false; + break; + + case DOM_VK_CAPS_LOCK: + capsLock = !capsLock; + break; + + default: + for (int i = 0; i < KEYMAP_LEN; i++) { + if (keyCode == KEYMAP[i][0]) { + keyCode = KEYMAP[i][1]; + break; + } + } + if (e->ctrlKey && e->altKey) { + pushEvent(getKeyPressedEvent(SB_KEY_CTRL_ALT(keyCode))); + } else if (e->ctrlKey && e->shiftKey) { + pushEvent(getKeyPressedEvent(SB_KEY_SHIFT_CTRL(keyCode))); + } else if (e->altKey && e->shiftKey) { + pushEvent(getKeyPressedEvent(SB_KEY_ALT_SHIFT(keyCode))); + } else if (e->ctrlKey) { + keyCode = to_lower(keyCode); + switch (keyCode) { + case 'b': + pushEvent(getMotionEvent(EVENT_TYPE_BACK, nullptr)); + break; + case 'm': + pushEvent(getMotionEvent(EVENT_TYPE_SHOW_MENU, nullptr)); + break; + case SB_KEY_PGUP: + pushEvent(getMotionEvent(EVENT_TYPE_PGUP, nullptr)); + break; + case SB_KEY_PGDN: + pushEvent(getMotionEvent(EVENT_TYPE_PGDN, nullptr)); + break; + case SB_KEY_UP: + pushEvent(getMotionEvent(EVENT_TYPE_UP, nullptr)); + break; + case SB_KEY_DN: + pushEvent(getMotionEvent(EVENT_TYPE_PGDN, nullptr)); + break; + default: + pushEvent(getKeyPressedEvent(SB_KEY_CTRL(keyCode))); + } + } else if (e->altKey) { + switch (keyCode) { + case 'D': + // browser keystroke + result = false; + break; + default: + pushEvent(getKeyPressedEvent(SB_KEY_ALT(keyCode))); + break; + } + } else if (e->shiftKey) { + bool shifted = false; + for (int i = 0; i < SHIFT_KEYMAP_LEN; i++) { + if (keyCode == SHIFT_KEYMAP[i][0]) { + keyCode = SHIFT_KEYMAP[i][1]; + shifted = true; + break; + } + } + if (shifted) { + pushEvent(getKeyPressedEvent(keyCode)); + } else { + pushEvent(getKeyPressedEvent(SB_KEY_SHIFT(keyCode))); + } + } else { + pushEvent(getKeyPressedEvent(capsLock ? keyCode : to_lower(keyCode))); + } + break; + } + return result; +} + +bool Runtime::handleMouse(int eventType, const EmscriptenMouseEvent *e) { + bool result = true; + switch (eventType) { + case EMSCRIPTEN_EVENT_MOUSEDOWN: + if (e->button == 2) { + pushEvent(getMotionEvent(EVENT_TYPE_SHOW_MENU, e)); + } else { + pushEvent(getMotionEvent(EVENT_TYPE_POINTER_PRESSED, e)); + } + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + pushEvent(getMotionEvent(EVENT_TYPE_POINTER_DRAGGED, e)); + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + pushEvent(getMotionEvent(EVENT_TYPE_POINTER_RELEASED, e)); + break; + default: + result = false; + break; + } + return result; +} + +void Runtime::pause(int timeout) { + if (timeout == -1) { + if (hasEvent()) { + MAEvent *event = popEvent(); + processEvent(*event); + delete event; + } + } else { + int slept = 0; + while (1) { + if (isBreak()) { + break; + } else if (hasEvent()) { + MAEvent *event = popEvent(); + processEvent(*event); + delete event; + } + emscripten_sleep(WAIT_INTERVAL); + slept += WAIT_INTERVAL; + if (timeout > 0 && slept > timeout) { + break; + } + } + } +} + +MAEvent Runtime::processEvents(int waitFlag) { + switch (waitFlag) { + case 1: + // wait for an event + _output->flush(true); + while (!hasEvent()) { + emscripten_sleep(WAIT_INTERVAL); + } + break; + case 2: + _output->flush(false); + pause(WAIT_INTERVAL); + break; + default: + emscripten_sleep(WAIT_INTERVAL); + } + + MAEvent event; + if (hasEvent()) { + MAEvent *nextEvent = popEvent(); + processEvent(*nextEvent); + event = *nextEvent; + delete nextEvent; + } else { + event.type = 0; + } + return event; +} + +void Runtime::processEvent(MAEvent &event) { + switch (event.type) { + case EVENT_TYPE_RESTART: + setRestart(); + break; + case EVENT_TYPE_SHOW_MENU: + _menuX = event.point.x; + _menuY = event.point.y; + showMenu(); + break; + case EVENT_TYPE_RESIZE: + resize(); + break; + case EVENT_TYPE_BACK: + setBack(); + break; + case EVENT_TYPE_PGUP: + _output->scroll(true, true); + break; + case EVENT_TYPE_PGDN: + _output->scroll(false, true); + break; + case EVENT_TYPE_UP: + _output->scroll(true, false); + break; + case EVENT_TYPE_DN: + _output->scroll(false, false); + break; + case EVENT_TYPE_KEY_PRESSED: + handleEvent(event); + if (event.key != -1 && isRunning()) { + dev_pushkey(event.key); + } + break; + + default: + handleEvent(event); + break; + } +} + +void Runtime::runShell() { + logEntered(); + audio_open(); + net_init(); + chdir("/basic"); + while (1) { + runMain(MAIN_BAS); + } +} + +void Runtime::setClipboardText(const char *text) { + EM_ASM_({ + navigator.clipboard.writeText(UTF8ToString($0)); + }, text); + clipboard = text; +} + +void Runtime::showCursor(CursorType cursorType) { + static CursorType _cursorType; + if (_cursorType != cursorType) { + _cursorType = cursorType; + EM_ASM_({ + document.body.style.cursor = UTF8ToString($0); + }, cursorType == kIBeam ? "text" : cursorType == kArrow ? "auto" : "pointer"); + } +} + +void System::editSource(strlib::String loadPath, bool restoreOnExit) { + logEntered(); + + strlib::String fileName; + int i = loadPath.lastIndexOf('/', 0); + if (i != -1) { + fileName = loadPath.substring(i + 1); + } else { + fileName = loadPath; + } + + strlib::String dirtyFile; + dirtyFile.append(" * "); + dirtyFile.append(fileName); + strlib::String cleanFile; + cleanFile.append(" - "); + cleanFile.append(fileName); + + int w = _output->getWidth(); + int h = _output->getHeight(); + int charWidth = _output->getCharWidth(); + int charHeight = _output->getCharHeight(); + int prevScreenId = _output->selectScreen(SOURCE_SCREEN); + TextEditInput *editWidget; + if (_editor != nullptr) { + editWidget = _editor; + editWidget->_width = w; + editWidget->_height = h; + } else { + editWidget = new TextEditInput(_programSrc, charWidth, charHeight, 0, 0, w, h); + } + auto *helpWidget = new TextEditHelpWidget(editWidget, charWidth, charHeight, false); + auto *widget = editWidget; + _modifiedTime = getModifiedTime(); + editWidget->updateUI(nullptr, nullptr); + editWidget->setLineNumbers(); + editWidget->setFocus(true); + + _output->clearScreen(); + _output->addInput(editWidget); + _output->addInput(helpWidget); + + if (gsb_last_line && isBreak()) { + String msg = "Break at line: "; + msg.append(gsb_last_line); + alert("Error", msg); + } else if (gsb_last_error && !isBack()) { + // program stopped with an error + editWidget->setCursorRow(gsb_last_line + editWidget->getSelectionRow() - 1); + alert("Error", gsb_last_errmsg); + } + + bool showStatus = !editWidget->getScroll(); + _srcRendered = false; + _output->setStatus(showStatus ? cleanFile : ""); + _output->redraw(); + _state = kEditState; + + while (_state == kEditState) { + MAEvent event = getNextEvent(); + switch (event.type) { + case EVENT_TYPE_POINTER_PRESSED: + if (!showStatus && widget == editWidget && event.point.x < editWidget->getMarginWidth()) { + _output->setStatus(editWidget->isDirty() ? dirtyFile : cleanFile); + _output->redraw(); + showStatus = true; + } + break; + case EVENT_TYPE_POINTER_RELEASED: + if (showStatus && event.point.x < editWidget->getMarginWidth() && editWidget->getScroll()) { + _output->setStatus(""); + _output->redraw(); + showStatus = false; + } + break; + case EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED: + if (editWidget->isDirty() && !editWidget->getScroll()) { + _output->setStatus(dirtyFile); + _output->redraw(); + } + break; + case EVENT_TYPE_KEY_PRESSED: + if (_userScreenId == -1) { + int sw = _output->getScreenWidth(); + bool redraw = true; + bool dirty = editWidget->isDirty(); + char *text; + + switch (event.key) { + case SB_KEY_F(2): + case SB_KEY_F(3): + case SB_KEY_F(4): + case SB_KEY_F(5): + case SB_KEY_F(6): + case SB_KEY_F(7): + case SB_KEY_F(8): + case SB_KEY_F(10): + case SB_KEY_F(11): + case SB_KEY_F(12): + case SB_KEY_MENU: + case SB_KEY_BREAK: + // unhandled keys + redraw = false; + break; + case SB_KEY_ESCAPE: + widget = editWidget; + helpWidget->hide(); + helpWidget->cancelMode(); + editWidget->setFocus(true); + break; + case SB_KEY_F(1): + widget = helpWidget; + helpWidget->createKeywordIndex(); + helpWidget->showPopup(-4, -2); + helpWidget->setFocus(true); + showStatus = false; + break; + case SB_KEY_F(9): + case SB_KEY_CTRL('r'): + _state = kRunState; + break; + case SB_KEY_CTRL('s'): + saveFile(editWidget, loadPath); + break; + case SB_KEY_CTRL('c'): + case SB_KEY_CTRL('x'): + text = widget->copy(event.key == (int)SB_KEY_CTRL('x')); + if (text) { + setClipboardText(text); + free(text); + } + break; + case SB_KEY_CTRL('v'): + text = getClipboardText(); + widget->paste(text); + free(text); + break; + case SB_KEY_CTRL('o'): + _output->selectScreen(USER_SCREEN1); + showCompletion(true); + _output->redraw(); + _state = kActiveState; + waitForBack(); + _output->selectScreen(SOURCE_SCREEN); + _state = kEditState; + break; + default: + redraw = widget->edit(event.key, sw, charWidth); + break; + } + if (editWidget->isDirty() != dirty && !editWidget->getScroll()) { + _output->setStatus(editWidget->isDirty() ? dirtyFile : cleanFile); + } + if (redraw) { + _output->redraw(); + } + } + } + + if (isBack() && widget == helpWidget) { + widget = editWidget; + helpWidget->hide(); + editWidget->setFocus(true); + _state = kEditState; + _output->redraw(); + } + + if (widget->isDirty()) { + int choice = -1; + if (isClosing()) { + choice = 0; + } else if (isBack()) { + const char *message = "The current file has not been saved.\n" + "Would you like to save it now?"; + choice = ask("Save changes?", message, isBack()); + } + if (choice == 0) { + widget->save(loadPath); + } else if (choice == 2) { + // cancel + _state = kEditState; + } + } + } + + if (_state == kRunState) { + // allow the editor to be restored on return + if (!_output->removeInput(editWidget)) { + trace("Failed to remove editor input"); + } + _editor = editWidget; + _editor->setFocus(false); + } else { + _editor = nullptr; + } + + // deletes editWidget unless it has been removed + _output->removeInputs(); + if (!isClosing() && restoreOnExit) { + _output->selectScreen(prevScreenId); + } + logLeaving(); +} + +// +// ma event handling +// +int maGetEvent(MAEvent *event) { + int result; + if (runtime->hasEvent()) { + MAEvent *nextEvent = runtime->popEvent(); + event->point = nextEvent->point; + event->type = nextEvent->type; + delete nextEvent; + result = 1; + } else { + result = 0; + } + return result; +} + +void maPushEvent(MAEvent *maEvent) { + runtime->pushEvent(maEvent); +} + +void maWait(int timeout) { + runtime->pause(timeout); +} + +// +// System platform methods +// +bool System::getPen3() { + bool result = false; + if (_touchX != -1 && _touchY != -1) { + result = true; + } else { + // get mouse + processEvents(0); + if (_touchX != -1 && _touchY != -1) { + result = true; + } + } + return result; +} + +// +// sbasic implementation +// +int osd_devinit(void) { + runtime->setRunning(true); + return 1; +} + +int osd_devrestore(void) { + runtime->setRunning(false); + return 0; +} + +// +// not implemented +// +void System::completeKeyword(int index) {} +void maHideVirtualKeyboard(void) {} +void maShowVirtualKeyboard(void) {} +void maUpdateScreen(void) {} diff --git a/src/platform/emcc/runtime.h b/src/platform/emcc/runtime.h new file mode 100644 index 00000000..e8b15422 --- /dev/null +++ b/src/platform/emcc/runtime.h @@ -0,0 +1,50 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2022 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#pragma once + +#include "ui/system.h" +#include + +struct Runtime : public System { + Runtime(); + virtual ~Runtime(); + + void addShortcut(const char *) {} + void alert(const char *title, const char *message); + int ask(const char *title, const char *prompt, bool cancel=true); + void browseFile(const char *url); + char *getClipboardText(); + int getFontSize() { return _output->getFontSize(); } + void enableCursor(bool enabled) {} + int handle(int event); + char *loadResource(const char *fileName); + void onRunCompleted() {} + void saveWindowRect() {} + bool handleKeyboard(int eventType, const EmscriptenKeyboardEvent *e); + bool handleMouse(int eventType, const EmscriptenMouseEvent *e); + bool hasEvent() { return _eventQueue && _eventQueue->size() > 0; } + void pause(int timeout); + void pushEvent(MAEvent *event) { _eventQueue->push(event); } + MAEvent *popEvent() { return _eventQueue->pop(); } + MAEvent processEvents(int waitFlag); + void processEvent(MAEvent &event); + bool run(const char *bas) { return execute(bas); } + void runShell(); + void setClipboardText(const char *text); + void setFontSize(int size); + void setLoadBreak(const char *url) {} + void setLoadPath(const char *url) {} + void setWindowRect(int x, int y, int width, int height) {} + void setWindowTitle(const char *title) {} + void share(const char *path) {} + void showCursor(CursorType cursorType); + +private: + Stack *_eventQueue; +}; diff --git a/src/platform/emcc/shell.html b/src/platform/emcc/shell.html new file mode 100644 index 00000000..ee71f9d9 --- /dev/null +++ b/src/platform/emcc/shell.html @@ -0,0 +1,134 @@ + + + + + + SmallBASIC + + + + + + + +
+
+
+
+ SmallBASIC ... +
+
+
Downloading...
+
+ +
+ + + {{{ SCRIPT }}} + + + diff --git a/src/platform/fltk/MainWindow.cxx b/src/platform/fltk/MainWindow.cxx index 7c84fb8a..d2fc0395 100644 --- a/src/platform/fltk/MainWindow.cxx +++ b/src/platform/fltk/MainWindow.cxx @@ -31,6 +31,7 @@ strlib::String recentPath[NUM_RECENT_ITEMS]; strlib::String recentLabel[NUM_RECENT_ITEMS]; int recentPosition[NUM_RECENT_ITEMS]; MainWindow *wnd; +Fl_Window *outputWindow; ExecState runMode = init_state; const char *fontCache = "fonts.txt"; @@ -110,13 +111,14 @@ bool MainWindow::basicMain(EditorWidget *editWidget, } } - Fl_Window *fullScreen = NULL; + Fl_Window *fullScreen = nullptr; Fl_Group *oldOutputGroup = _outputGroup; int old_w = _out->w(); int old_h = _out->h(); int old_x = _out->x(); int old_y = _out->y(); int interactive = opt_interactive; + outputWindow = this; if (!toolExec) { if (opt_ide == IDE_NONE) { @@ -130,6 +132,7 @@ bool MainWindow::basicMain(EditorWidget *editWidget, setTitle(fullScreen, filename); _profile->restoreAppPosition(fullScreen); _outputGroup = fullScreen; + outputWindow = fullScreen; resizeDisplay(0, 0, w(), h()); hide(); } else { @@ -148,10 +151,11 @@ bool MainWindow::basicMain(EditorWidget *editWidget, } while (restart); + outputWindow = this; opt_interactive = interactive; bool was_break = (runMode == break_state); - if (fullScreen != NULL) { + if (fullScreen != nullptr) { _profile->setAppPosition(*fullScreen); fullScreen->remove(_out); delete fullScreen; @@ -220,7 +224,7 @@ void MainWindow::close_other_tabs(Fl_Widget *w, void *eventData) { int n = _tabGroup->children(); Fl_Group *items[n]; for (int c = 0; c < n; c++) { - items[c] = NULL; + items[c] = nullptr; Fl_Group *child = (Fl_Group *)_tabGroup->child(c); if (child != selected && gw_editor == getGroupWidget(child)) { EditorWidget *editWidget = (EditorWidget *)child->child(0); @@ -230,7 +234,7 @@ void MainWindow::close_other_tabs(Fl_Widget *w, void *eventData) { } } for (int c = 0; c < n; c++) { - if (items[c] != NULL) { + if (items[c] != nullptr) { _tabGroup->remove(items[c]); delete items[c]; } @@ -327,7 +331,7 @@ void MainWindow::help_contents_brief(Fl_Widget *w, void *eventData) { int start, end; char *selection = editWidget->getSelection(&start, &end); const char *help = getBriefHelp(selection); - if (help != NULL) { + if (help != nullptr) { editWidget->getTty()->print(help); editWidget->getTty()->print("\n"); } @@ -364,19 +368,19 @@ void MainWindow::export_file(Fl_Widget *w, void *eventData) { if (dev_fopen(handle, _exportFile, DEV_FILE_OUTPUT)) { const char *data = editWidget->data(); if (token[0]) { - vsncat(buffer, sizeof(buffer), "# ", token, "\n", NULL); + vsncat(buffer, sizeof(buffer), "# ", token, "\n", nullptr); dev_fwrite(handle, (byte *)buffer, strlen(buffer)); } if (!dev_fwrite(handle, (byte *)data, editWidget->dataLength())) { - vsncat(buffer, sizeof(buffer), "Failed to write: ", _exportFile.c_str(), NULL); + vsncat(buffer, sizeof(buffer), "Failed to write: ", _exportFile.c_str(), nullptr); statusMsg(rs_err, buffer); } else { vsncat(buffer, sizeof(buffer), "Exported", editWidget->getFilename(), " to ", - _exportFile.c_str(), NULL); + _exportFile.c_str(), nullptr); statusMsg(rs_ready, buffer); } } else { - vsncat(buffer, sizeof(buffer), "Failed to open: ", _exportFile.c_str(), NULL); + vsncat(buffer, sizeof(buffer), "Failed to open: ", _exportFile.c_str(), nullptr); statusMsg(rs_err, buffer); } dev_fclose(handle); @@ -684,9 +688,9 @@ void MainWindow::scanPlugIns(Fl_Menu_Bar *menu) { snprintf(path, sizeof(path), "%s/%s", packageHome, pluginHome); DIR *dp = opendir(path); - while (dp != NULL) { + while (dp != nullptr) { struct dirent *e = readdir(dp); - if (e == NULL) { + if (e == nullptr) { break; } const char *filename = e->d_name; @@ -764,7 +768,6 @@ int arg_cb(int argc, char **argv, int &i) { return 1; case 'm': - opt_loadmod = 1; strcpy(opt_modpath, argv[i + 1]); i += 2; return 1; @@ -1074,7 +1077,7 @@ void MainWindow::new_file(Fl_Widget *w, void *eventData) { } void MainWindow::open_file(Fl_Widget *w, void *eventData) { - FileWidget *fileWidget = NULL; + FileWidget *fileWidget = nullptr; Fl_Group *openFileGroup = findTab(gw_file); if (!openFileGroup) { openFileGroup = createTab(gw_file, fileTabName); @@ -1186,7 +1189,7 @@ EditorWidget *MainWindow::getEditor(const char *fullpath) { } } } - return NULL; + return nullptr; } /** @@ -1428,7 +1431,7 @@ int MainWindow::handle(int e) { } void MainWindow::loadHelp(const char *path) { - if (!getHelp()->loadHelp(path) && getEditor() != NULL) { + if (!getHelp()->loadHelp(path) && getEditor() != nullptr) { getEditor()->statusMsg("Failed to open help file"); } } @@ -1441,16 +1444,16 @@ void MainWindow::loadIcon() { #if defined(WIN32) HICON ico = (HICON) icon(); if (!ico) { - ico = (HICON) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(101), + ico = (HICON) LoadImage(GetModuleHandle(nullptr), MAKEINTRESOURCE(101), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR | LR_SHARED); if (!ico) { - ico = LoadIcon(NULL, IDI_APPLICATION); + ico = LoadIcon(nullptr, IDI_APPLICATION); } } icon((char *)ico); #else #include "icon.h" - Fl_RGB_Image *image = new Fl_PNG_Image(NULL, sb_desktop_128x128_png, sb_desktop_128x128_png_len); + Fl_RGB_Image *image = new Fl_PNG_Image(nullptr, sb_desktop_128x128_png, sb_desktop_128x128_png_len); icon(image); #endif } @@ -1497,7 +1500,7 @@ int BaseWindow::handle(int e) { } break; } - if (_mainSystem != NULL) { + if (_mainSystem != nullptr) { _mainSystem->handle(e); } break; diff --git a/src/platform/fltk/runtime.cxx b/src/platform/fltk/runtime.cxx index 222ee28a..c5cbad65 100644 --- a/src/platform/fltk/runtime.cxx +++ b/src/platform/fltk/runtime.cxx @@ -16,6 +16,7 @@ #include extern MainWindow *wnd; +extern Fl_Window *outputWindow; extern System *g_system; static auto *onlineUrl = "http://smallbasic.github.io/samples/index.bas"; @@ -234,12 +235,21 @@ void Runtime::setFontSize(int size) { _output->setFontSize(size); } -void Runtime::setWindowSize(int width, int height) { - wnd->size(width, height); +void Runtime::setWindowRect(int x, int y, int width, int height) { + if (wnd != outputWindow && outputWindow != nullptr) { + if (width > 0 && height > 0) { + outputWindow->size(width, height); + } + if (x > 0 && y > 0) { + outputWindow->position(x, y); + } + } } void Runtime::setWindowTitle(const char *title) { - wnd->label(title); + if (wnd != outputWindow && outputWindow != nullptr) { + outputWindow->label(title); + } } void Runtime::showCursor(CursorType cursorType) { @@ -301,6 +311,10 @@ int maGetEvent(MAEvent *event) { return result; } +void maPushEvent(MAEvent *maEvent) { + // not implemented +} + void maWait(int timeout) { if (timeout == -1) { Fl::wait(); diff --git a/src/platform/fltk/runtime.h b/src/platform/fltk/runtime.h index fd591d96..8f670411 100755 --- a/src/platform/fltk/runtime.h +++ b/src/platform/fltk/runtime.h @@ -35,7 +35,7 @@ struct Runtime : public System { void setFontSize(int size); void setLoadBreak(const char *url) {} void setLoadPath(const char *url) {} - void setWindowSize(int width, int height); + void setWindowRect(int x, int y, int width, int height); void setWindowTitle(const char *title); void share(const char *path) {} void showCursor(CursorType cursorType); diff --git a/src/platform/sdl/editor.cpp b/src/platform/sdl/editor.cpp index 811a0e5a..ff8230b3 100644 --- a/src/platform/sdl/editor.cpp +++ b/src/platform/sdl/editor.cpp @@ -234,6 +234,13 @@ void System::editSource(String loadPath, bool restoreOnExit) { editWidget->setCursorRow(gsb_last_line); statusMessage.update(editWidget, _output, true); } else if (gsb_last_error && !isBack()) { + if (gsb_last_errmsg[0]) { + // trim any trailing new-line character + char *nl = strrchr(gsb_last_errmsg, '\n'); + if (nl) { + *nl = '\0'; + } + } String lastFile(gsb_last_file); if (lastFile.endsWith(".sbu")) { _output->setStatus(!gsb_last_errmsg[0] ? "Unit error" : gsb_last_errmsg); diff --git a/src/platform/sdl/main.cpp b/src/platform/sdl/main.cpp index e660f0bb..5182883d 100644 --- a/src/platform/sdl/main.cpp +++ b/src/platform/sdl/main.cpp @@ -322,7 +322,6 @@ int main(int argc, char* argv[]) { ide_option = IDE_INTERNAL; break; case 'm': - opt_loadmod = 1; if (optarg) { strcpy(opt_modpath, optarg); } diff --git a/src/platform/sdl/runtime.cpp b/src/platform/sdl/runtime.cpp index 61ec26e8..5ee82745 100644 --- a/src/platform/sdl/runtime.cpp +++ b/src/platform/sdl/runtime.cpp @@ -37,14 +37,11 @@ #define PAUSE_DEBUG_LAUNCH 750 #define PAUSE_DEBUG_STEP 50 #define MAIN_BAS "__main_bas__" -#define OPTIONS_BOX_WIDTH_EXTRA 1 -#define OPTIONS_BOX_BG 0xd2d1d0 -#define OPTIONS_BOX_FG 0x3e3f3e #define EVENT_TYPE_RESTART 101 Runtime *runtime; -SDL_mutex *g_lock = NULL; -SDL_cond *g_cond = NULL; +SDL_mutex *g_lock = nullptr; +SDL_cond *g_cond = nullptr; SDL_bool g_debugPause = SDL_FALSE; SDL_bool g_debugBreak = SDL_FALSE; SDL_bool g_debugTrace = SDL_TRUE; @@ -73,15 +70,13 @@ MAEvent *getKeyPressedEvent(int keycode, int nativeKey) { Runtime::Runtime(SDL_Window *window) : System(), - _menuX(0), - _menuY(0), _fullscreen(false), - _graphics(NULL), - _eventQueue(NULL), + _graphics(nullptr), + _eventQueue(nullptr), _window(window), - _cursorHand(NULL), - _cursorArrow(NULL), - _cursorIBeam(NULL) { + _cursorHand(nullptr), + _cursorArrow(nullptr), + _cursorIBeam(nullptr) { runtime = this; } @@ -90,17 +85,17 @@ Runtime::~Runtime() { delete _output; delete _eventQueue; delete _graphics; - runtime = NULL; - _output = NULL; - _eventQueue = NULL; - _graphics = NULL; + runtime = nullptr; + _output = nullptr; + _eventQueue = nullptr; + _graphics = nullptr; SDL_FreeCursor(_cursorHand); SDL_FreeCursor(_cursorArrow); SDL_FreeCursor(_cursorIBeam); - _cursorHand = NULL; - _cursorArrow = NULL; - _cursorIBeam = NULL; + _cursorHand = nullptr; + _cursorArrow = nullptr; + _cursorIBeam = nullptr; } void Runtime::alert(const char *title, const char *message) { @@ -225,7 +220,7 @@ void Runtime::debugStep(TextEditInput *edit, TextEditHelpWidget *help, bool cont if (size > 0) { edit->gotoLine(buf); net_print(g_debugee, "v\n"); - help->reload(NULL); + help->reload(nullptr); bool endChar = false; do { size = net_read(g_debugee, buf, sizeof(buf)); @@ -270,14 +265,6 @@ bool Runtime::toggleFullscreen() { return _fullscreen; } -void Runtime::pushEvent(MAEvent *event) { - _eventQueue->push(event); -} - -MAEvent *Runtime::popEvent() { - return _eventQueue->pop(); -} - int Runtime::runShell(const char *startupBas, bool runWait, int fontScale, int debugPort) { logEntered(); @@ -310,7 +297,7 @@ int Runtime::runShell(const char *startupBas, bool runWait, int fontScale, int d SDL_DetachThread(thread); } - if (startupBas != NULL) { + if (startupBas != nullptr) { String bas = startupBas; if (opt_ide == IDE_INTERNAL) { runEdit(bas.c_str()); @@ -346,7 +333,7 @@ int Runtime::runShell(const char *startupBas, bool runWait, int fontScale, int d char *Runtime::loadResource(const char *fileName) { logEntered(); char *buffer = System::loadResource(fileName); - if (buffer == NULL && strcmp(fileName, MAIN_BAS) == 0) { + if (buffer == nullptr && strcmp(fileName, MAIN_BAS) == 0) { buffer = (char *)malloc(main_bas_len + 1); memcpy(buffer, main_bas, main_bas_len); buffer[main_bas_len] = '\0'; @@ -444,7 +431,7 @@ void Runtime::pollEvents(bool blocking) { if (isActive() && !isRestart()) { SDL_Event ev; if (blocking ? SDL_WaitEvent(&ev) : SDL_PollEvent(&ev)) { - MAEvent *maEvent = NULL; + MAEvent *maEvent = nullptr; SDL_Keymod mod; switch (ev.type) { case SDL_TEXTINPUT: @@ -475,7 +462,7 @@ void Runtime::pollEvents(bool blocking) { } else if (ev.key.keysym.sym == SDLK_b && (ev.key.keysym.mod & KMOD_CTRL)) { setBack(); } else if (ev.key.keysym.sym == SDLK_BACKSPACE && - get_focus_edit() == NULL && + get_focus_edit() == nullptr && ((ev.key.keysym.mod & KMOD_CTRL) || !isRunning())) { setBack(); } else if (!isEditing() && ev.key.keysym.sym == SDLK_PAGEUP && @@ -512,7 +499,7 @@ void Runtime::pollEvents(bool blocking) { break; } } - if (maEvent == NULL && + if (maEvent == nullptr && // Non-numeric-keypad, Control and Alt keys (((ev.key.keysym.sym >= SDLK_KP_1 && ev.key.keysym.sym <= SDLK_KP_0) && ev.key.keysym.mod != KMOD_NUM) || @@ -571,7 +558,7 @@ void Runtime::pollEvents(bool blocking) { } break; } - if (maEvent != NULL) { + if (maEvent != nullptr) { pushEvent(maEvent); } } @@ -621,11 +608,16 @@ void Runtime::processEvent(MAEvent &event) { } } -void Runtime::setWindowSize(int width, int height) { +void Runtime::setWindowRect(int x, int y, int width, int height) { logEntered(); - SDL_SetWindowSize(_window, width, height); - _graphics->resize(width, height); - resize(); + if (width > 0 && height > 0) { + SDL_SetWindowSize(_window, width, height); + _graphics->resize(width, height); + resize(); + } + if (x > 0 && y > 0) { + SDL_SetWindowPosition(_window, x, y); + } } void Runtime::setWindowTitle(const char *title) { @@ -633,7 +625,7 @@ void Runtime::setWindowTitle(const char *title) { SDL_SetWindowTitle(_window, "SmallBASIC"); } else { const char *slash = strrchr(title, '/'); - if (slash == NULL) { + if (slash == nullptr) { slash = title; } else { slash++; @@ -662,7 +654,7 @@ void Runtime::showCursor(CursorType cursorType) { void Runtime::onResize(int width, int height) { logEntered(); - if (_graphics != NULL) { + if (_graphics != nullptr) { int w = _graphics->getWidth(); int h = _graphics->getHeight(); if (w != width || h != height) { @@ -673,104 +665,6 @@ void Runtime::onResize(int width, int height) { } } -void Runtime::optionsBox(StringList *items) { - int backScreenId = _output->getScreenId(true); - int frontScreenId = _output->getScreenId(false); - _output->selectBackScreen(MENU_SCREEN); - - int width = 0; - int charWidth = _output->getCharWidth(); - List_each(String *, it, *items) { - char *str = (char *)(* it)->c_str(); - int w = (strlen(str) * charWidth); - if (w > width) { - width = w; - } - } - width += (charWidth * OPTIONS_BOX_WIDTH_EXTRA); - - int charHeight = _output->getCharHeight(); - int textHeight = charHeight + (charHeight / 3); - int height = textHeight * items->size(); - - if (!_menuX) { - _menuX = _output->getWidth() - (width + charWidth * 2); - } - if (!_menuY) { - _menuY = _output->getHeight() - height; - } - - if (_menuX + width >= _output->getWidth()) { - _menuX = _output->getWidth() - width; - } - if (_menuY + height >= _output->getHeight()) { - _menuY = _output->getHeight() - height; - } - - int y = 0; - int index = 0; - int selectedIndex = -1; - int releaseCount = 0; - - _output->insetMenuScreen(_menuX, _menuY, width, height); - - List_each(String *, it, *items) { - char *str = (char *)(* it)->c_str(); - FormInput *item = new MenuButton(index, selectedIndex, str, 0, y, width, textHeight); - _output->addInput(item); - item->setColor(OPTIONS_BOX_BG, OPTIONS_BOX_FG); - index++; - y += textHeight; - } - - _output->redraw(); - showCursor(kArrow); - while (selectedIndex == -1 && !isClosing()) { - MAEvent ev = processEvents(true); - if (ev.type == EVENT_TYPE_KEY_PRESSED) { - if (ev.key == 27) { - break; - } else if (ev.key == 13) { - selectedIndex = _output->getMenuIndex(); - break; - } else if (ev.key == SB_KEY_UP) { - _output->handleMenu(true); - } else if (ev.key == SB_KEY_DOWN) { - _output->handleMenu(false); - } - } - if (ev.type == EVENT_TYPE_POINTER_RELEASED) { - showCursor(kArrow); - if (++releaseCount == 2) { - break; - } - } - } - - _output->removeInputs(); - _output->selectBackScreen(backScreenId); - _output->selectFrontScreen(frontScreenId); - _menuX = 0; - _menuY = 0; - if (selectedIndex != -1) { - if (_systemMenu == NULL && isRunning() && - !form_ui::optionSelected(selectedIndex)) { - dev_clrkb(); - dev_pushkey(selectedIndex); - } else { - MAEvent *maEvent = new MAEvent(); - maEvent->type = EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED; - maEvent->optionsBoxButtonIndex = selectedIndex; - pushEvent(maEvent); - } - } else { - delete [] _systemMenu; - _systemMenu = NULL; - } - - _output->redraw(); -} - SDL_Rect Runtime::getWindowRect() { SDL_Rect result; if (_fullscreen) { @@ -803,7 +697,7 @@ char *Runtime::getClipboardText() { result = strdup(text); SDL_free(text); } else { - result = NULL; + result = nullptr; } return result; } @@ -811,7 +705,7 @@ char *Runtime::getClipboardText() { void Runtime::onRunCompleted() { SDL_SetWindowPosition(_window, _saveRect.x, _saveRect.y); SDL_SetWindowSize(_window, _saveRect.w, _saveRect.h); - setWindowSize(_saveRect.w, _saveRect.h); + setWindowRect(_saveRect.x, _saveRect.y, _saveRect.w, _saveRect.h); } void Runtime::saveWindowRect() { @@ -853,6 +747,10 @@ int maGetEvent(MAEvent *event) { return result; } +void maPushEvent(MAEvent *maEvent) { + runtime->pushEvent(maEvent); +} + void maWait(int timeout) { runtime->pause(timeout); } diff --git a/src/platform/sdl/runtime.h b/src/platform/sdl/runtime.h index 2eb34a67..5dd0e58f 100644 --- a/src/platform/sdl/runtime.h +++ b/src/platform/sdl/runtime.h @@ -41,19 +41,18 @@ struct Runtime : public System { bool hasEvent() { return _eventQueue && _eventQueue->size() > 0; } void pause(int timeout); void pollEvents(bool blocking); - MAEvent *popEvent(); + MAEvent *popEvent() { return _eventQueue->pop(); } MAEvent processEvents(int waitFlag); void processEvent(MAEvent &event); - void pushEvent(MAEvent *event); + void pushEvent(MAEvent *event) { _eventQueue->push(event); } void saveWindowRect(); - void setWindowSize(int width, int height); + void setWindowRect(int x, int y, int width, int height); void setWindowTitle(const char *title); void share(const char *path) {} void showCursor(CursorType cursorType); int runShell(const char *startupBas, bool once, int fontScale, int debugPort); char *loadResource(const char *fileName); void logStack(int line, bool subOrFunc); - void optionsBox(StringList *items); void onResize(int w, int h); void onRunCompleted(); void setClipboardText(const char *text); @@ -62,7 +61,6 @@ struct Runtime : public System { SDL_Rect getWindowRect(); private: - int _menuX, _menuY; bool _fullscreen; SDL_Rect _windowRect; SDL_Rect _saveRect; diff --git a/src/platform/web/main.cpp b/src/platform/web/main.cpp index 46297898..4cfd23ac 100644 --- a/src/platform/web/main.cpp +++ b/src/platform/web/main.cpp @@ -25,22 +25,28 @@ uint32_t g_start = 0; uint32_t g_maxTime = 2000; bool g_graphicText = true; bool g_noExecute = false; -struct MHD_Connection *g_connection; +bool g_json = false; +char *execBas = nullptr; +MHD_Connection *g_connection; StringList g_cookies; +String g_path; +String g_data; static struct option OPTIONS[] = { - {"help", no_argument, NULL, 'h'}, - {"verbose", no_argument, NULL, 'v'}, - {"file-permitted", no_argument, NULL, 'f'}, - {"no-execute", no_argument, NULL, 'x'}, - {"port", optional_argument, NULL, 'p'}, - {"run", optional_argument, NULL, 'r'}, - {"width", optional_argument, NULL, 'w'}, - {"height", optional_argument, NULL, 'e'}, - {"command", optional_argument, NULL, 'c'}, - {"graphic-text", optional_argument, NULL, 'g'}, - {"max-time", optional_argument, NULL, 't'}, - {"module", optional_argument, NULL, 'm'}, + {"file-permitted", no_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {"json-content", no_argument, nullptr, 'j'}, + {"no-execute", no_argument, nullptr, 'x'}, + {"verbose", no_argument, nullptr, 'v'}, + {"command", optional_argument, nullptr, 'c'}, + {"exec-bas", optional_argument, nullptr, 'i'}, + {"graphic-text", optional_argument, nullptr, 'g'}, + {"height", optional_argument, nullptr, 'e'}, + {"max-time", optional_argument, nullptr, 't'}, + {"module", optional_argument, nullptr, 'm'}, + {"port", optional_argument, nullptr, 'p'}, + {"run", optional_argument, nullptr, 'r'}, + {"width", optional_argument, nullptr, 'w'}, {0, 0, 0, 0} }; @@ -68,7 +74,7 @@ void show_help() { (int)sizeof(var_int_t), (int)sizeof(var_num_t)); fprintf(stdout, "usage: sbasicw [options]...\n"); int i = 0; - while (OPTIONS[i].name != NULL) { + while (OPTIONS[i].name != nullptr) { fprintf(stdout, OPTIONS[i].has_arg ? " -%c, --%s=''\n" : " -%c, --%s\n", OPTIONS[i].val, OPTIONS[i].name); @@ -80,7 +86,7 @@ void show_help() { void log(const char *format, ...) { va_list args; va_start(args, format); - unsigned size = format == NULL ? 0 : vsnprintf(NULL, 0, format, args); + unsigned size = format == nullptr ? 0 : vsnprintf(nullptr, 0, format, args); va_end(args); if (size) { @@ -92,7 +98,7 @@ void log(const char *format, ...) { buf[size] = '\0'; char date[18]; - time_t t = time(NULL); + time_t t = time(nullptr); struct tm *p = localtime(&t); strftime(date, sizeof(date), "%Y%m%d %H:%M:%S", p); fprintf(stdout, "%s %s\n", date, buf); @@ -105,49 +111,41 @@ MHD_Result accept_cb(void *cls, const struct sockaddr *addr, socklen_t addrlen) return MHD_YES; } -MHD_Response *execute(struct MHD_Connection *connection, const char *bas) { - const char *width = - MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "width"); - const char *height = - MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "height"); - const char *command = - MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "command"); - const char *graphicText = - MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "graphic-text"); - const char *accept = - MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); - - if (width != NULL) { +MHD_Response *execute(MHD_Connection *connection, const char *bas) { + const char *width = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "width"); + const char *height = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "height"); + const char *command = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "command"); + const char *graphicText = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "graphic-text"); + const char *accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); + const char *contentType = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); + + if (width != nullptr) { os_graf_mx = atoi(width); } - if (height != NULL) { + if (height != nullptr) { os_graf_my = atoi(height); } - if (graphicText != NULL) { + if (graphicText != nullptr) { g_graphicText = atoi(graphicText) > 0; } - if (command != NULL) { + if (command != nullptr) { strcpy(opt_command, command); } - log("%s dim:%dX%d", bas, os_graf_mx, os_graf_my); + log("%s dim:%dX%d [accept=%s, content-type=%s]", bas, os_graf_mx, os_graf_my, accept, contentType); g_connection = connection; g_canvas.reset(); g_start = dev_get_millisecond_count(); g_canvas.setGraphicText(g_graphicText); - g_canvas.setJSON((strncmp(accept, "application/json", 16) == 0)); + g_canvas.setJSON(g_json || (accept && strncmp(accept, "application/json", 16) == 0)); g_cookies.removeAll(); sbasic_main(bas); - g_connection = NULL; + g_connection = nullptr; String page = g_canvas.getPage(); - MHD_Response *response = - MHD_create_response_from_buffer(page.length(), (void *)page.c_str(), - MHD_RESPMEM_MUST_COPY); + MHD_Response *response = MHD_create_response_from_buffer(page.length(), (void *)page.c_str(), MHD_RESPMEM_MUST_COPY); List_each(String *, it, g_cookies) { String *next = (*it); - MHD_add_response_header(response, - MHD_HTTP_HEADER_SET_COOKIE, - next->c_str()); + MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, next->c_str()); } return response; } @@ -159,15 +157,20 @@ MHD_Response *serve_file(const char *path) { if (!fstat(fd, &stbuf)) { response = MHD_create_response_from_fd(stbuf.st_size, fd); } else { - response = NULL; + response = nullptr; } return response; } -MHD_Response *get_response(struct MHD_Connection *connection, const char *path) { - MHD_Response *response = NULL; +MHD_Response *get_response(MHD_Connection *connection, const char *path) { + MHD_Response *response = nullptr; struct stat stbuf; - if (path[0] == '\0') { + + g_path = path; + + if (execBas && stat(execBas, &stbuf) != -1 && S_ISREG(stbuf.st_mode)) { + response = execute(connection, execBas); + } else if (path[0] == '\0') { if (stat("index.bas", &stbuf) != -1 && S_ISREG(stbuf.st_mode)) { response = execute(connection, "index.bas"); } else { @@ -175,7 +178,7 @@ MHD_Response *get_response(struct MHD_Connection *connection, const char *path) } } else if (strcmp(path, "favicon.ico") == 0) { response = serve_file(path); - } else if (strstr(path, "..") == NULL) { + } else if (strstr(path, "..") == nullptr) { const char *dot = strrchr(path, '.'); if (dot && !g_noExecute && strncasecmp(dot, ".bas", 4) == 0 && stat(path, &stbuf) != -1 && S_ISREG(stbuf.st_mode)) { @@ -190,7 +193,7 @@ MHD_Response *get_response(struct MHD_Connection *connection, const char *path) // server callback // see: /usr/share/doc/libmicrohttpd-dev/examples MHD_Result access_cb(void *cls, - struct MHD_Connection *connection, + MHD_Connection *connection, const char *url, const char *method, const char *version, @@ -205,26 +208,30 @@ MHD_Result access_cb(void *cls, return MHD_YES; } // clear context pointer - *ptr = NULL; + *ptr = nullptr; - if (upload_data != NULL) { + if (upload_data != nullptr) { + // curl -H "Accept: application/json" -d '{"productId": 123456, "quantity": 100}' http://localhost:8080/foo size_t size = OPT_CMD_SZ - 1; if (*upload_data_size < size) { size = *upload_data_size; } - strncpy(opt_command, upload_data, size); + g_data.clear(); + g_data.append(upload_data, size); + *upload_data_size = 0; + return MHD_YES; } MHD_Result result; - struct MHD_Response *response = get_response(connection, url + 1); - if (response != NULL) { - result = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_Response *response = get_response(connection, url + 1); + if (response != nullptr) { + int code = g_canvas.getPage().length() ? MHD_HTTP_OK : MHD_HTTP_NO_CONTENT; + result = MHD_queue_response(connection, code, response); } else { String error; error.append("File not found: ").append(url); log(error.c_str()); - response = MHD_create_response_from_buffer(error.length(), (void *)error.c_str(), - MHD_RESPMEM_MUST_COPY); + response = MHD_create_response_from_buffer(error.length(), (void *)error.c_str(), MHD_RESPMEM_MUST_COPY); result = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); } MHD_destroy_response(response); @@ -234,11 +241,11 @@ MHD_Result access_cb(void *cls, int main(int argc, char **argv) { init(); int port = 8080; - char *runBas = NULL; + char *runBas = nullptr; while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "hvfxp:t:m::r:w:e:c:g:", OPTIONS, &option_index); + int c = getopt_long(argc, argv, "hvfxjp:t:m::r:w:e:c:g:i:", OPTIONS, &option_index); if (c == -1) { break; } @@ -280,14 +287,21 @@ int main(int argc, char **argv) { g_maxTime = atoi(optarg); break; case 'm': - opt_loadmod = 1; if (optarg) { strcpy(opt_modpath, optarg); } break; + case 'i': + if (optarg) { + execBas = strdup(optarg); + } + break; case 'x': g_noExecute = true; break; + case 'j': + g_json = true; + break; default: show_help(); exit(1); @@ -295,24 +309,24 @@ int main(int argc, char **argv) { } } - if (runBas != NULL) { + if (runBas != nullptr) { g_canvas.reset(); g_start = dev_get_millisecond_count(); sbasic_main(runBas); puts(g_canvas.getPage().c_str()); } else { fprintf(stdout, "Starting SmallBASIC web server on port:%d\n", port); - MHD_Daemon *d = - MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port, - &accept_cb, NULL, - &access_cb, NULL, MHD_OPTION_END); - if (d == NULL) { + MHD_Daemon *d = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD, port, + &accept_cb, nullptr, + &access_cb, nullptr, MHD_OPTION_END); + if (d == nullptr) { fprintf(stderr, "startup failed\n"); return 1; } getc(stdin); MHD_stop_daemon(d); } + free(execBas); return 0; } @@ -372,7 +386,7 @@ void osd_write(const char *str) { } char *dev_gets(char *dest, int maxSize) { - return NULL; + return nullptr; } void lwrite(const char *buf) { @@ -386,7 +400,7 @@ void lwrite(const char *buf) { // struct ValueIteratorClosure { ValueIteratorClosure(int el) : - _result(NULL), + _result(nullptr), _count(0), _element(el) { } @@ -420,33 +434,37 @@ int dev_setenv(const char *key, const char *value) { const char *dev_getenv(const char *key) { const char *result; - if (g_connection != NULL) { + if (strcmp(key, "path") == 0) { + result = g_path.c_str(); + } else if (strcmp(key, "data") == 0) { + result = g_data.c_str(); + } else if (g_connection != nullptr) { result = MHD_lookup_connection_value(g_connection, MHD_COOKIE_KIND, key); - if (result == NULL) { + if (result == nullptr) { result = MHD_lookup_connection_value(g_connection, MHD_HEADER_KIND, key); } - if (result == NULL) { + if (result == nullptr) { result = MHD_lookup_connection_value(g_connection, MHD_GET_ARGUMENT_KIND, key); } } else { - result = NULL; + result = nullptr; } return result; } int dev_env_count() { - int count = 0; - if (g_connection != NULL) { - count += MHD_get_connection_values(g_connection, MHD_COOKIE_KIND, countIterator, NULL); - count += MHD_get_connection_values(g_connection, MHD_HEADER_KIND, countIterator, NULL); - count += MHD_get_connection_values(g_connection, MHD_GET_ARGUMENT_KIND, countIterator, NULL); + int count = 2; + if (g_connection != nullptr) { + count += MHD_get_connection_values(g_connection, MHD_COOKIE_KIND, countIterator, nullptr); + count += MHD_get_connection_values(g_connection, MHD_HEADER_KIND, countIterator, nullptr); + count += MHD_get_connection_values(g_connection, MHD_GET_ARGUMENT_KIND, countIterator, nullptr); } return count; } const char *dev_getenv_n(int n) { - const char *result = NULL; - if (g_connection != NULL) { + const char *result = nullptr; + if (g_connection != nullptr) { ValueIteratorClosure closure(n); MHD_get_connection_values(g_connection, MHD_COOKIE_KIND, valueIterator, &closure); MHD_get_connection_values(g_connection, MHD_HEADER_KIND, valueIterator, &closure); @@ -456,6 +474,10 @@ const char *dev_getenv_n(int n) { return result; } +void dev_delay(uint32_t ms) { + usleep(1000 * ms); +} + // // not implemented // @@ -478,5 +500,4 @@ void v_create_image(var_p_t var) {} void v_create_form(var_p_t var) {} void v_create_window(var_p_t var) {} void dev_show_page() {} -void dev_delay(uint32_t ms) {} void dev_log_stack(const char *keyword, int type, int line) {} diff --git a/src/ui/audio.cpp b/src/ui/audio.cpp index 995859b6..dba7440b 100644 --- a/src/ui/audio.cpp +++ b/src/ui/audio.cpp @@ -7,7 +7,6 @@ // #define MINIAUDIO_IMPLEMENTATION -#define DR_WAV_IMPLEMENTATION #include "config.h" #include @@ -16,7 +15,6 @@ #include "include/osd.h" #include "ui/strlib.h" #include "ui/audio.h" -#include "lib/miniaudio/extras/dr_wav.h" #include "lib/miniaudio/miniaudio.h" extern "C" { @@ -87,11 +85,12 @@ static void data_callback(ma_device *device, void *output, const void *input, ma if (queue.empty()) { usleep(MILLIS_TO_MICROS(10)); } else { + ma_uint64 framesRead; queuePos = (queuePos + 1) % queue.size(); Sound *sound = queue[queuePos]; if (sound->_decoder != nullptr) { // play audio track - ma_uint64 framesRead = ma_decoder_read_pcm_frames(sound->_decoder, output, frameCount); + ma_decoder_read_pcm_frames(sound->_decoder, output, frameCount, &framesRead); if (framesRead == 0) { // finished playing queue.pop(false); @@ -99,13 +98,13 @@ static void data_callback(ma_device *device, void *output, const void *input, ma } else if (sound->_start == 0) { // start new sound sound->_start = dev_get_millisecond_count(); - ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount); + ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount, &framesRead); } else if (dev_get_millisecond_count() - sound->_start > sound->_duration) { // sound has timed out queue.pop(false); } else { // continue sound - ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount); + ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount, &framesRead); } } } @@ -133,7 +132,7 @@ static void setup_format(ma_format format, ma_uint32 channels, ma_uint32 sampleR } static void device_start() { - if (ma_device_get_state(&device) != MA_STATE_STARTED) { + if (ma_device_get_state(&device) != ma_device_state_started) { ma_result result = ma_device_start(&device); if (result != MA_SUCCESS) { err_throw("Failed to start audio [%d]", result); @@ -144,11 +143,15 @@ static void device_start() { bool audio_open() { bool result; ma_backend backends[] = { +#if defined(_EMCC) + ma_backend_webaudio +#else ma_backend_alsa, ma_backend_jack, ma_backend_pulseaudio, ma_backend_wasapi, - ma_backend_dsound + ma_backend_dsound, +#endif }; if (ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), NULL, &context) != MA_SUCCESS) { result = false; diff --git a/src/ui/image.cpp b/src/ui/image.cpp index 53dd54c1..ddab2f71 100644 --- a/src/ui/image.cpp +++ b/src/ui/image.cpp @@ -464,23 +464,23 @@ void cmd_image_hide(var_s *self, var_s *) { void cmd_image_save(var_s *self, var_s *) { unsigned id = map_get_int(self, IMG_BID, -1); ImageBuffer *image = get_image(id); - var_t *array = nullptr; - dev_file_t *file = nullptr; - if (code_peek() == kwTYPE_SEP) { - file = eval_filep(); - } else { - array = par_getvar_ptr(); - } - + dev_file_t *file; + var_t *array; + var_t var; bool saved = false; if (!prog_error && image != nullptr) { unsigned w = image->_width; unsigned h = image->_height; - if (file != nullptr && file->open_flags == DEV_FILE_OUTPUT) { - if (!encode_png_file(file->name, image->_image, w, h)) { + switch (code_peek()) { + case kwTYPE_SEP: + file = eval_filep(); + if (file != nullptr && file->open_flags == DEV_FILE_OUTPUT && + !encode_png_file(file->name, image->_image, w, h)) { saved = true; } - } else if (array != nullptr) { + break; + case kwTYPE_VAR: + array = par_getvar_ptr(); v_tomatrix(array, h, w); // x0 x1 x2 (w=3,h=2) // y0 rgba rgba rgba ypos=0 @@ -497,6 +497,16 @@ void cmd_image_save(var_s *self, var_s *) { } } saved = true; + break; + default: + v_init(&var); + eval(&var); + if (var.type == V_STR && !prog_error && + !lodepng_encode32_file(var.v.p.ptr, image->_image, w, h)) { + saved = true; + } + v_free(&var); + break; } } diff --git a/src/ui/rgb.h b/src/ui/rgb.h index 24134896..7049015a 100644 --- a/src/ui/rgb.h +++ b/src/ui/rgb.h @@ -39,7 +39,7 @@ inline void v_get_argb(int64_t c, uint8_t &a, uint8_t &r, uint8_t &g, uint8_t &b #define v_get_argb_px(a, r, g, b) (a << 24 | (r << 16) | (g << 8) | (b)) -#if defined(_SDL) +#if defined(_SDL) || defined(_EMCC) // SDL_PACKEDORDER_XRGB // A = byte 3 // R = byte 2 diff --git a/src/ui/system.cpp b/src/ui/system.cpp index 94078fa0..d6561d90 100644 --- a/src/ui/system.cpp +++ b/src/ui/system.cpp @@ -60,7 +60,11 @@ #define FONT_MIN 20 #define FONT_MAX 200 -#if defined(_SDL) +#define OPTIONS_BOX_WIDTH_EXTRA 1 +#define OPTIONS_BOX_BG 0xd2d1d0 +#define OPTIONS_BOX_FG 0x3e3f3e + +#if defined(_SDL) || defined(_EMCC) #define MK_MENU(l, a) " " l " " a " " #else #define MK_MENU(l, a) " " l @@ -122,6 +126,8 @@ System::System() : _initialFontSize(0), _fontScale(100), _userScreenId(-1), + _menuX(0), + _menuY(0), _modifiedTime(0), _mainBas(false), _buttonPressed(false), @@ -175,7 +181,6 @@ bool System::execute(const char *bas) { enableCursor(true); opt_file_permitted = 1; - opt_loadmod = 0; _output->selectScreen(USER_SCREEN1); _output->resetFont(); _output->flush(true); @@ -602,6 +607,104 @@ void System::logStack(const char *keyword, int type, int line) { #endif } +void System::optionsBox(StringList *items) { + int backScreenId = _output->getScreenId(true); + int frontScreenId = _output->getScreenId(false); + _output->selectBackScreen(MENU_SCREEN); + + int width = 0; + int charWidth = _output->getCharWidth(); + List_each(String *, it, *items) { + char *str = (char *)(* it)->c_str(); + int w = (strlen(str) * charWidth); + if (w > width) { + width = w; + } + } + width += (charWidth * OPTIONS_BOX_WIDTH_EXTRA); + + int charHeight = _output->getCharHeight(); + int textHeight = charHeight + (charHeight / 3); + int height = textHeight * items->size(); + + if (!_menuX) { + _menuX = _output->getWidth() - (width + charWidth * 2); + } + if (!_menuY) { + _menuY = _output->getHeight() - height; + } + + if (_menuX + width >= _output->getWidth()) { + _menuX = _output->getWidth() - width; + } + if (_menuY + height >= _output->getHeight()) { + _menuY = _output->getHeight() - height; + } + + int y = 0; + int index = 0; + int selectedIndex = -1; + int releaseCount = 0; + + _output->insetMenuScreen(_menuX, _menuY, width, height); + + List_each(String *, it, *items) { + char *str = (char *)(* it)->c_str(); + FormInput *item = new MenuButton(index, selectedIndex, str, 0, y, width, textHeight); + _output->addInput(item); + item->setColor(OPTIONS_BOX_BG, OPTIONS_BOX_FG); + index++; + y += textHeight; + } + + _output->redraw(); + showCursor(kArrow); + while (selectedIndex == -1 && !isClosing()) { + MAEvent ev = processEvents(true); + if (ev.type == EVENT_TYPE_KEY_PRESSED) { + if (ev.key == 27) { + break; + } else if (ev.key == 13) { + selectedIndex = _output->getMenuIndex(); + break; + } else if (ev.key == SB_KEY_UP) { + _output->handleMenu(true); + } else if (ev.key == SB_KEY_DOWN) { + _output->handleMenu(false); + } + } + if (ev.type == EVENT_TYPE_POINTER_RELEASED) { + showCursor(kArrow); + if (++releaseCount == 2) { + break; + } + } + } + + _output->removeInputs(); + _output->selectBackScreen(backScreenId); + _output->selectFrontScreen(frontScreenId); + _menuX = 0; + _menuY = 0; + if (selectedIndex != -1) { + if (_systemMenu == NULL && isRunning() && + !form_ui::optionSelected(selectedIndex)) { + dev_clrkb(); + dev_pushkey(selectedIndex); + } else { + MAEvent *maEvent = new MAEvent(); + maEvent->type = EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED; + maEvent->optionsBoxButtonIndex = selectedIndex; + maPushEvent(maEvent); + } + } else { + delete [] _systemMenu; + _systemMenu = NULL; + } + + _output->redraw(); +} + char *System::readSource(const char *fileName) { _activeFile.clear(); char *buffer; @@ -982,7 +1085,7 @@ void System::showMenu() { _systemMenu[index++] = MENU_PASTE; _systemMenu[index++] = MENU_SELECT_ALL; } -#if defined(_SDL) || defined(_FLTK) +#if defined(_SDL) || defined(_FLTK) || defined(_EMCC) items->add(new String(MENU_STR_BACK)); _systemMenu[index++] = MENU_BACK; #else @@ -1006,17 +1109,19 @@ void System::showMenu() { _systemMenu[index++] = MENU_RESTART; } #endif -#if !defined(_SDL) && !defined(_FLTK) +#if !defined(_SDL) && !defined(_FLTK) && !defined(_EMCC) items->add(new String(MENU_STR_KEYPAD)); _systemMenu[index++] = MENU_KEYPAD; #endif if (_mainBas) { +#if !defined(_EMCC) sprintf(buffer, MENU_STR_FONT, _fontScale - FONT_SCALE_INTERVAL); items->add(new String(buffer)); sprintf(buffer, MENU_STR_FONT, _fontScale + FONT_SCALE_INTERVAL); items->add(new String(buffer)); _systemMenu[index++] = MENU_ZOOM_UP; _systemMenu[index++] = MENU_ZOOM_DN; +#endif sprintf(buffer, MENU_STR_EDITOR, opt_ide == IDE_NONE ? MENU_STR_OFF : MENU_STR_ON); items->add(new String(buffer)); _systemMenu[index++] = MENU_EDITMODE; @@ -1027,7 +1132,7 @@ void System::showMenu() { sprintf(buffer, MENU_STR_AUDIO, (opt_mute_audio ? MENU_STR_OFF : MENU_STR_ON)); items->add(new String(buffer)); _systemMenu[index++] = MENU_AUDIO; -#if !defined(_SDL) && !defined(_FLTK) +#if !defined(_SDL) && !defined(_FLTK) && !defined(_EMCC) if (!_mainBas && !_activeFile.empty()) { items->add(new String(MENU_STR_SHORT)); items->add(new String(MENU_STR_SHARE)); @@ -1035,9 +1140,11 @@ void System::showMenu() { _systemMenu[index++] = MENU_SHARE; } #endif +#if !defined(_EMCC) items->add(new String(MENU_STR_SCREEN)); _systemMenu[index++] = MENU_SCREENSHOT; -#if defined(_SDL) || defined(_FLTK) +#endif +#if defined(_SDL) || defined(_FLTK) || defined(_EMCC) items->add(new String(MENU_STR_BACK)); _systemMenu[index++] = MENU_BACK; #endif diff --git a/src/ui/system.h b/src/ui/system.h index e61cce93..40abf8f0 100755 --- a/src/ui/system.h +++ b/src/ui/system.h @@ -61,17 +61,17 @@ struct System { virtual void browseFile(const char *url) = 0; virtual MAEvent processEvents(int waitFlag) = 0; virtual char *loadResource(const char *fileName); - virtual void optionsBox(StringList *items) = 0; + virtual void optionsBox(StringList *items); virtual void onRunCompleted() = 0; virtual void saveWindowRect() = 0; - virtual void setWindowSize(int width, int height) = 0; + virtual void setWindowRect(int x, int y, int width, int height) = 0; virtual void setWindowTitle(const char *title) = 0; virtual void share(const char *path) = 0; virtual void showCursor(CursorType cursorType) = 0; virtual void setClipboardText(const char *text) = 0; virtual char *getClipboardText() = 0; -protected: + protected: void editSource(strlib::String loadPath, bool restoreOnExit); bool execute(const char *bas); bool fileExists(strlib::String &path); @@ -137,6 +137,8 @@ struct System { int _initialFontSize; int _fontScale; int _userScreenId; + int _menuX; + int _menuY; uint32_t _modifiedTime; bool _mainBas; bool _buttonPressed; diff --git a/src/ui/utils.h b/src/ui/utils.h index c99af5a2..dc879c04 100644 --- a/src/ui/utils.h +++ b/src/ui/utils.h @@ -28,7 +28,7 @@ #include #define deviceLog(...) __android_log_print(ANDROID_LOG_INFO, \ "smallbasic", __VA_ARGS__) -#elif defined(_SDL) || defined(_FLTK) +#elif defined(_SDL) || defined(_FLTK) || defined(_EMCC) void appLog(const char *format, ...); #define deviceLog(...) appLog(__VA_ARGS__) #endif diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 6b5ae6bd..c6fe0f0b 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -28,6 +28,7 @@ extern System *g_system; #define WINDOW_INSET "insetTextScreen" #define WINDOW_SETFONT "setFont" #define WINDOW_SETSIZE "setSize" +#define WINDOW_SETLOCATION "setLocation" #define WINDOW_THEME "theme" // returns the next set of string variable arguments as a String list @@ -111,7 +112,13 @@ void cmd_window_set_font(var_s *self, var_s *) { void cmd_window_set_size(var_s *self, var_s *) { var_int_t width, height; par_massget("II", &width, &height); - g_system->setWindowSize(width, height); + g_system->setWindowRect(-1, -1, width, height); +} + +void cmd_window_set_location(var_s *self, var_s *) { + var_int_t x, y; + par_massget("II", &x, &y); + g_system->setWindowRect(x, y, -1, -1); } void cmd_window_get_theme(var_s *, var_s *retval) { @@ -186,6 +193,7 @@ extern "C" void v_create_window(var_p_t var) { v_create_func(var, WINDOW_INSET, cmd_window_inset); v_create_func(var, WINDOW_SETFONT, cmd_window_set_font); v_create_func(var, WINDOW_SETSIZE, cmd_window_set_size); + v_create_func(var, WINDOW_SETLOCATION, cmd_window_set_location); v_create_func(var, WINDOW_THEME, cmd_window_get_theme); }