Singkong Programming Language Interpreter (c) Noprianto , 2019-2024 Website: nopri.github.io License: Free to use or redistribute, no warranty Version: 9.2 (2024-February-16 +07:00) Singkong is based on Monkey.java: an open source, simple implementation of Monkey programming language interpreter in Java. Monkey.java is based on monkey.py: an open source, simple implementation of Monkey programming language interpreter in Python. Monkey.java and monkey.py, (c) Noprianto , 2019. monkey.py is based on code (in Go) in book: Writing an interpreter in Go Singkong is developed on Java 8 with -source 1.5 -target 1.5, using only Java 5.0-compatible application programming interface. ----------------------------------------------------------------------------- Bundled JDBC Drivers (compatible with Java 5.0 or later): Apache Derby (Apache License 2.0): Network Server, Driver (Embedded, Client) PostgreSQL (BSD-2-Clause License) ============================================================================= 1. What is Singkong Programming Language 2. System requirements 3. Getting started 4. Identifier, keywords, data types, and operators 5. Built-in functions and modules 6. If condition 7. Repeat loop 8. GUI application development 9. Database application development 10. Web application development 11. Simple HTTP client 12. Working with threads 13. Calling Java method 14. Embedding Singkong into another applications 15. Deployment 16. Differences with Monkey Programming Language 17. Example: GUI: components 18. Example: GUI: layout 19. Example: GUI: Singkong information 20. Example: GUI: event handlers 21. Example: GUI: printing 22. Example: GUI: simple text file editor 23. Example: GUI: timer 24. Example: Database: create table, insert, update, select, GUI 25. Example: Web: variables, GET, POST, JSON 26. Example: Java: custom dialog 27. Example: Monkey programming language interpreter in Singkong 28. Example: Working with Comma-Separated Values file 29. Example: Drawing: simple painting program ----------------------------------------------------------------------------- 1. What is Singkong Programming Language Singkong is case-insensitive, dynamically typed, procedural, and interpreted programming language that runs on Java Virtual Machine (Java 5.0 or later). It has numbers, booleans, strings, arrays, hash maps, dates, first-class functions, built-in functions, GUI components, database connections, and other features. Singkong program can call Java methods and Singkong interpreter can be embedded and integrated into another applications. Singkong interpreter is distributed as single jar file. It comes with simple GUI editor/interactive evaluator/database tool and can be run on graphical or text user interfaces. Singkong is based on Monkey.java and inspired by Monkey Programming Language. ----------------------------------------------------------------------------- 2. System requirements Java Runtime Environment version 5.0 or later. If only text-based environment is available, Singkong interactive evaluator will fallback to text only version and built-in functions will adapt appropriately. ----------------------------------------------------------------------------- 3. Getting started Please download Singkong.jar from its website. The jar file can be saved into any directory - no system wide installation is required. Using portable Java installation, Singkong can be run from external/portable storage device. If GUI is available, Singkong.jar features interactive evaluator, variable inspector, simple text editor, database tool, and documentation viewer. To run Singkong in text mode when GUI is available, please set property SINGKONG=0 (-DSINGKONG=0). This setting has no effect when GUI is not available. To disable some (or all) built-in functions, please set property DISABLE (-DDISABLE) to comma-separated list of functions. If the first item in the list is -, then only built-in functions in the list are enabled. To disable all built-in functions, please set -DDISABLE=-. - Standalone - No command line argument: interactive java -jar Singkong.jar Singkong 9.2 Press ENTER to quit > var hello = "Hello World" > hello "Hello World" > - No command line argument: interactive (GUI available) java -jar Singkong.jar (double-clicking Singkong.jar may also work) - No command line argument: interactive (run in text mode, GUI available) java -DSINGKONG=0 -jar Singkong.jar - No command line argument: interactive (disable built-in: system) java -DDISABLE=system -jar Singkong.jar - No command line argument: interactive (disable built-in: system and info) java -DDISABLE=system,info -jar Singkong.jar - No command line argument: interactive (enable only built-in: len and print) java -DDISABLE=-,len,print -jar Singkong.jar - Command line argument: try to interpret as file java -jar Singkong.jar test.singkong If exception occurred: interpret the argument as Singkong code java -jar Singkong.jar "println(1,2,3)" 1 2 3 - Run directly the main class (and setting the class path): java -cp Singkong.jar com.noprianto.singkong.Singkong java -cp Singkong.jar com.noprianto.singkong.Singkong test.singkong java -cp Singkong.jar com.noprianto.singkong.Singkong "println(1,2,3)" java -DSINGKONG=0 -cp Singkong.jar com.noprianto.singkong.Singkong "println(1,2,3)" - Library Please read: Embedding Singkong into another applications Note: please use built-in function: interactive, to get information whether Singkong program is run in Singkong interactive evaluator/editor. ----------------------------------------------------------------------------- 4. Identifier, keywords, data types, and operators Identifier starts with letter or underscore and optionally followed by letters, numbers, or underscores. Built-in function names and keywords cannot be used as identifier. Assignment is done using var statement: var name = "singkong" var f = fn(name) { println(name) } Note: - Statement optionally ends with ; - Block statement starts with { and ends with } - Comment starts with # and ends with ; - Multi-line comment is supported Comment: # this is a multi-line comment ; println("Singkong") # this is another comment; println("Programming") # this is also a comment ; println("Language") Keywords: # ELSE FALSE FN IF LET NULL REPEAT RETURN TRUE VAR Note: to get list of keywords, please use built-in function keywords. Data types: Related functions: builtins, eval, is, is_array_and_of, is_array_of, is_array_of_component_of, hash, print, println, puts, string, type, types, variables - NULL - null - null literal - null may also returned from built-in functions - null is not printed. To print null, please use print, println, puts, or message. - NUMBER - any integer and decimal - precision: 10240, default scale: 4 - to change the number scale, please use built-in function number_scale - operators: + - * / == != %(remainder) ^(power) < <= > >= - related functions: abs, array_number, chr, integer, integer_gcd, number, number_boolean, number_group, number_to_byte, random, round, sort_number, words_en, words_id - system related functions: delay - Singkong related functions: require - thread related functions: thread, thread_alive, thread_join - trigonometric functions (degrees): sin, cos, tan, asin, acos, atan - degrees radians conversion functions: to_degrees, to_radians - hyperbolic functions: sinh, cosh, tanh - natural exponential, logarithm, square/cube root functions: exp, log, log10, sqrt, cbrt - base conversion: from_bin, from_hex, from_oct, to_bin, to_hex, to_oct - bitwise operation: number_xor, number_or, number_and, number_not - bit shift operation: left_shift, right_shift, unsigned_right_shift - sorting: by value - constant values: _e, _pi > number_scale(15) true > _e() 2.718281828459045 > _pi() 3.141592653589793 - number scale setting example (since Singkong version 4.7): > _pi() 3.1416 > number_scale(8) true > _pi() 3.14159265 > number_scale(16) true > _pi() 3.1415926535897930 - From STRING to NUMBER: number: > number("1.23") 1.2300 > number("a") 0 > number("a", -1) -1 > number("a", 0, false) 0 > type(number("a", 0, true)) "NULL" - mean, median, mode, range (using util module): > load_module("util") > var a = [0, 1, 2, 3, 4, 3, 2, 1, 0, 0] > mean(a) 1.6000 > median(a) 1.5000 > mode(a) [0, 3] > range_(a) 4 - number grouping: built-in function: number_group using util module: number_group_c_p number_group_p_c number_group_s_c number_group_s_p - more functions in util module: binomial_coefficient binomial_distribution factorial geometric_distribution_failure geometric_distribution_success poisson_distribution - BOOLEAN - true or false - operators: == != & | ! - related functions: number_boolean, boolean_xor, sort_boolean - sorting: by value - STRING - double-quoted - arbitrary length string - operators: +(concatenation) -(remove) ==(equals,case-sensitive) != *(repeat) - equals (case-insensitive): please use built-in function equals - Using ==(equals,case-sensitive) operator: > "Singkong" == "singkong" false > "Singkong" == "Singkong" true - index operator - modify STRING: please use built-in function set - related functions: array_string, base64_decode, base64_encode, call, center, count, cr, crlf, dir, empty, endswith, equals, eval, in, index, isalnum, isalpha, isdigit, islower, isupper, join, left, len, lf, lower, matches, md5, md5_file, newline, ord, parse_string, quote, random_string, replace, right, set, sha1, sha1_file, sha256, sha256_file, sha384, sha384_file, sha512, sha512_file, slice, sort_string, split, startswith, stat, tab, trim, upper - special characters: cr, crlf, lf, newline, quote, tab - file related functions: abs, append, copy, copy_resource, delete, mkdir, read, rename, temp_file, write - HTTP client related functions: http_delete, http_get, http_get_file, http_head, http_post, http_post_override, http_put, url_decode, url_encode - system related functions: cwd, inet_address_local, separator, user, userhome - Singkong related functions: load, load_file_or_resource, load_module, load_resource - extended: x_base64_decode_file, x_base64_encode_file - sorting: lexicographically - simple string or file encryption/decryption: please use functions from util module: - string: load_module("util") var a = "Hello World" var b = "Singkong" var c = simple_string_encrypt(a, b) var d = simple_string_decrypt(c, b) println(a, b, a == d) - file: load_module("util") var a = "input.txt" var b = "Singkong" var c = "input.data" var d = "output.txt" write(a, "Hello World") var e = simple_file_encrypt(a, b, c) var f = simple_file_decrypt(c, b, d) println(e, f, md5_file(a) == md5_file(d)) - ARRAY - [] - array of mixed data types - operators: +(add), -(remove) == != - add and extend: > [1,2] + 3 [1, 2, 3] > [1,2] + [3] [1, 2, [3]] > array_extend([1,2], [3]) [1, 2, 3] > array_extend_all([[1,2], [3,4], 5]) [1, 2, 3, 4, 5] - Using == operator: > [] == [] true > [1,2] == [1,2] true > [1,2] == [2,1] false > [[1,2]] == [[2,1]] false > [1,2] == [1,2,3] false - equals (the same elements, not necessarily in the same order): please use built-in function array_equals > [1,2] == [2,1] false > array_equals([1,2], [2,1]) true > array_equals([1,2], [1,2]) true - index operator - modify ARRAY: please use built-in function set - related functions: array, array_equals, array_extend, array_extend_all, array_number, array_string, average, call, count, each, empty, first, in, index, join, last, len, max, min, parse_array, pop, push, random, range, rest, reverse, shuffle, slice, sort_array, sort_boolean, sort_date, sort_hash, sort_number, sort_string, sum - array check functions: is_array_and_of, is_array_of, is_array_of_component_of - file related functions: dir, read_byte, write_byte - CGI related functions: cgi_contents - HTTP client related functions: http_response_ok - system related functions: arguments, inet_address, runtime_version, system - Singkong related functions: builtins, modules, singkong_interpreter - byte array: number_to_byte_array, string_from_byte_array, string_to_byte_array > number_to_byte_array(123) [123] > number_to_byte_array(1234) [4, -46] > string_to_byte_array("Singkong") [83, 105, 110, 103, 107, 111, 110, 103] > string_from_byte_array([83, 105, 110, 103, 107, 111, 110, 103]) "Singkong" - sorting: by length - rectangular array: array whose each element is an array, and all elements have the same length - rectangular array functions: is_rect_array, is_rect_array_of, rect_array_size, rect_array_size_of - sort rectangular array of number based on index: please use function from util module: sort_rect_array_of_number_by_index - rectangular array example: > var a = [] > var b = [[], []] > var c = [[1, 2, 3], ["Singkong", "Programming", "Language"]] > var d = [[1, 2, 3], [4, 5, 6]] > is_rect_array(a) false > is_rect_array(b) true > is_rect_array(c) true > is_rect_array_of(c, "NUMBER") false > is_rect_array_of(d, "NUMBER") true > rect_array_size(c) [2, 3] > rect_array_size_of(c, "NUMBER") > rect_array_size_of(c, "NUMBER") == null true > rect_array_size_of(d, "NUMBER") [2, 3] > load_module("util") > sort_rect_array_of_number_by_index([[1,3], [3,2], [2,1]], 1) [[2, 1], [3, 2], [1, 3]] > sort_rect_array_of_number_by_index([[1,3], [3,2], [2,1]], 0) [[1, 3], [2, 1], [3, 2]] - rectangular array and matrix: please use functions from rect_array_util module: add_rect_array_of add_rect_array_of_number cross_product_3d_rect_array_column_of cross_product_3d_rect_array_column_of_number cross_product_3d_rect_array_row_of cross_product_3d_rect_array_row_of_number determinant_rect_array_of determinant_rect_array_of_number inverse_rect_array_of inverse_rect_array_of_number is_constant_rect_array_of is_constant_rect_array_of_number is_diagonal_rect_array_of is_diagonal_rect_array_of_number is_identity_rect_array_of_number is_lower_triangular_rect_array_of is_lower_triangular_rect_array_of_number is_rect_array_column_of is_rect_array_column_of_number is_rect_array_row_of is_rect_array_row_of_number is_square_rect_array_of is_square_rect_array_of_number is_symmetric_rect_array_of is_symmetric_rect_array_of_number is_upper_triangular_rect_array_of is_upper_triangular_rect_array_of_number is_zero_rect_array_of is_zero_rect_array_of_number minor_rect_array_of minor_rect_array_of_number mul_rect_array_of mul_rect_array_of_number mul_scalar_rect_array_of mul_scalar_rect_array_of_number rect_array_not_row_col sub_rect_array_of sub_rect_array_of_number trace_rect_array_of trace_rect_array_of_number transpose_rect_array_of transpose_rect_array_of_number - copy: var a = ["hello", "world"] var b = [] each(a, fn(e,i) { var b = b + e }) set(a, 1, "hello world") println(a, b) - copy (using array_copy function from util module, since Singkong version 4.6): load_module("util") var a = ["hello", "world"] var b = array_copy(a) set(a, 1, "hello world") println(a, b) - diff (using array_diff function from util module): > array_diff([1,2,3,4,5], [1,3,5]) [2, 4] - From STRING to ARRAY: parse_array using default mode (eval expression): > parse_array("[1+2]", true) [3] > parse_array("[1+2]", true, 0) [3] > parse_array("[1+2, fn(){true}()]", true) [3, true] alternative 1: parse only ARRAY, BOOLEAN, HASH (uses strings for keys), NULL, NUMBER, STRING, other types: STRING representation (pass 1 as third argument): > parse_array("[1+2]", true, 1) ["(1 + 2)"] > parse_array("[1+2, fn(){true}()]", true, 1) ["(1 + 2)", "fn() { true; } ()"] alternative 2: parse only ARRAY, BOOLEAN, HASH (keys must be strings), NULL, NUMBER, STRING, other types: returns NULL (or empty ARRAY) (pass 2 as third argument): > type(parse_array("[1+2]", true, 2)) "NULL" > parse_array("[1+2]", false, 2) [] > parse_array("[1, 2]", true, 2) [1, 2] - set related functions: please use functions from set_util module: create_relation_from_array create_set_from_array get_relation_part inverse_bijective_function_set is_antisymmetric_relation_set is_bijective_function_set is_function_set is_injective_function_set is_reflexive_relation_set is_relation is_relation_set is_same_set is_sub_set is_surjective_function_set is_symmetric_relation_set is_transitive_relation_set set_diff set_intersection set_power set_product set_union - more functions in util module: standard_deviation standard_deviation_ standard_deviation_sample variance variance_ variance_sample - HASH - {} - hash table/dictionary - any data type can be used as key - hash maintains insertion-order - operators: +(add dictionary), -(remove) == != - Using == operator: > {} == {} true > {1:2} == {1:2} true > {1:2} == {2:1} false > {1.0:2} == {1:2} true > {1:2, 3:4} == {3:4, 1:2} true > {1:1+1, 3:2+2} == {3:4.0, 1.0:2.0} true > {1:2, 3:4} == {1.0001:2, 3:4} false - modify HASH: please use built-in function set - using string function: if optional second argument (BOOLEAN) is true (default: false), use strings for keys. - related functions: empty, keys, len, parse_hash, random, sort_hash, values - file related functions: properties_read, properties_write, stat - CGI related functions: cgi_header, cgi_get, cgi_post, cgi_post_hash - system related functions: env, info - Singkong related functions: singkong - sorting: by length - copy: var a = {"hello": "world"} var b = {} + a set(a, "hello", "hello world") println(a, b) - copy (using hash_copy function from util module, since Singkong version 4.6): load_module("util") var a = {"hello": "world"} var b = hash_copy(a) set(a, "hello", "hello world") println(a, b) - From STRING to HASH: parse_hash using default mode (eval expression): > parse_hash("{1: 2+3}", true) {1: 5} > parse_hash("{1: 2+3}", true, 0) {1: 5} > parse_hash("{1: 2+3, true: fn(){true}()}", true) {1: 5, true: true} alternative 1: uses strings for keys, parse only ARRAY, BOOLEAN, HASH, NULL, NUMBER, STRING, other types: STRING representation (pass 1 as third argument): > parse_hash("{1: 2+3}", true, 1) {"1": "(2 + 3)"} > parse_hash("{1: 2+3, true: fn(){true}()}", true, 1) {"1": "(2 + 3)", "true": "fn() { true; } ()"} > parse_hash("{1: 2}", true, 1) {"1": 2} > parse_hash("{true: true}", true, 1) {"true": true} > parse_hash(string({"1": 2}, true), true, 1) {"1": 2} > parse_hash(string({1: 2}), true, 1) {"1": 2} alternative 2: parse only ARRAY, BOOLEAN, HASH (keys must be strings), NULL, NUMBER, STRING, other types: NULL (or empty HASH) (pass 2 as third argument): > type(parse_hash("{1: 2+3}", true, 2)) "NULL" > parse_hash("{1: 2+3}", false, 2) {} > type(parse_hash("{1: 2}", true, 2)) "NULL" > type(parse_hash("{true: true}", true, 2)) "NULL" > parse_hash(string({"1": 2}, true), true, 2) {"1": 2} > type(parse_hash(string({1: 2}), true, 2)) "NULL" - DATE - @ @Y @YY @YYY @YYYY @YYYYM @YYYYMM @YYYYMMD @YYYYMMDD @YYYYMMDDh @YYYYMMDDhh @YYYYMMDDhhm @YYYYMMDDhhmm @YYYYMMDDhhmms @YYYYMMDDhhmmss - related functions: date, datetime, day, days_of_month, diff, format_date, format_datetime, format_diff, format_time, hour, is_leap_year, minute, month, part, second, sort_date, year - file related functions: stat - From DATE to NUMBER: number: > number(@2023) 1672506000180 - From STRING to DATE: date or datetime: > date("2023-10-30") 2023-10-30 00:00:00 > datetime("2023-10-30") 2023-10-30 09:14:11 > date("") 2023-10-30 09:14:38 > date("", @2023) 2023-01-01 00:00:00 > date("", @2023, false) 2023-01-01 00:00:00 > type(date("", @2023, true)) "NULL" - From ARRAY to DATE: date or datetime: > date([2023, 10, 30]) 2023-10-30 00:00:00 > date([2023, 10, 30, 9, 15, 20]) 2023-10-30 00:00:00 > datetime([2023, 10, 30, 9, 15, 20]) 2023-10-30 09:15:20 > date([]) 2023-10-30 09:18:27 > date([], @2023) 2023-01-01 00:00:00 > date([], @2023, false) 2023-01-01 00:00:00 > type(date([], @2023, true)) "NULL" - sorting: before/after - FUNCTION - fn() - fn(arg) - fn(arg, arg,...) - function definition: var statement - function call: function name followed by (, optional comma-separated arguments, and ) - documentation string (since Singkong version 4.6): without documentation string: > var f = fn(x) {x} > f fn(x) { x; } > help(f) "" with documentation string: > var f = fn(x) "test" {x} > f [documentation string] test > help(f) "[documentation string] test" - related functions: do, each, help, param - Singkong related functions: load, load_file_or_resource, load_module, load_resource - thread related functions: thread - nested function is supported: code: var a = fn() { println("a") var b = fn() { println("b") var c = fn() { println("c") var d = fn() { println("d") } d() } c() } b() } a() output: a b c d - variable scope in nested function (new in Singkong version 6.0): code: var x = "Hello" println("X: " + x) var a = fn() { println("Function A (X): " + x) var b = fn() { println("Function B in A (X): " + x) var c = fn() { # x defined in c; var x = "World" println("Function C in B (local variable X): " + x) var d = fn() { println("Function D in C (using variable X in C): " + x) } d() } c() # original x; println("B (original X): " + x) } b() # not found; println(y) } a() output: X: Hello Function A (X): Hello Function B in A (X): Hello Function C in B (local variable X): World Function D in C (using variable X in C): World B (original X): Hello ERROR: [line: 22] identifier not found: y ERROR: [line: 22] identifier not found: y - BUILTIN - built-in function - related functions: builtins, disabled, help, param - extended: built-in functions that require minimum Java version > 5.0. function name prefix: x_ these functions are always available if the requirement is not met: returns NULL - COMPONENT - Please read: GUI application development - related functions: add, add_e, add_n, add_s, add_w, button_image, clear, closing, component, component_info, component_type, components, config, custom_dialog_close, event, event_focus, event_frame, event_keyboard_frame, event_mouse, event_mouse_frame, fonts, frame, frame_close, frame_image, frame_location, frame_top, get, grid_add, grid_clear, grid_remove, gui, hide, menubar, panel_add, panel_clear, panel_remove, popup_component, popup_hide, popup_show, printer, radio_group, remove, remove_e, remove_n, remove_s, remove_w, reset, resizable, screen, show, size, start, start_timer, statusbar, stop, stop_timer, tab_add, tab_clear, tab_remove, table_add, table_bottom, table_center, table_column, table_column_count, table_column_info, table_column_name, table_get_value, table_left, table_middle, table_print, table_remove, table_right, table_row_count, table_scroll, table_set_value, table_top, timer, timer_running, title, wait - GUI input/output functions: confirm, directory, input, open, message, password, save (if GUI is not available: simpler text-based version) - GUI dialogs: color_chooser, custom_dialog, login_dialog, panel_dialog - Drawing: draw_width, draw_rect, fill_rect, draw_arc, fill_arc, draw_oval, fill_oval, draw_round_rect, fill_round_rect, draw_string, draw_line, draw_polygon, fill_polygon, draw_polyline, draw_read, draw_write_png, draw_write_jpg, draw_write_bmp, draw_get_pixel, draw_set_pixel - Clipboard: clipboard_get, clipboard_set - Sound: beep, play_sound - array of component check: is_array_of_component_of - extended: x_edit_print - user interface related functions: please use functions from ui_util module: table_add_fill table_add_row_fill table_fill table_get_array_ table_get_array_number table_get_array_string table_to_html table_to_text - user interface, calendar related functions: please use functions from ui_calendar module: create_calendar create_calendar_basic create_calendar_basic_compact create_calendar_simple create_calendar_simple_compact - DATABASE - Please read: Database application development - related functions: database, database_connected, database_metadata, query - To work with relational database without using SQL command directly: built-in module db_util: create_field_from_array db_connect db_connect_embed db_connect_embed_ db_connect_embed_user db_create_table db_create_table_ db_create_table_derby db_create_table_derby_ db_create_table_embed db_create_table_embed_ db_create_table_postgresql db_create_table_postgresql_ db_delete db_delete_ db_driver db_insert db_insert_ db_last db_last_derby db_last_embed db_last_postgresql db_query_simple db_query_single db_run_query db_select db_select_ db_select_all db_select_all_ db_select_derby db_select_derby_ db_select_embed db_select_embed_ db_select_postgresql db_select_postgresql_ db_update db_update_ query_result ----------------------------------------------------------------------------- 5. Built-in functions and modules To get a list of built-in functions, please call builtins() ["_E", "_PI", "ABS", "ACOS", "ADD", "ADD_E", "ADD_N", "ADD_S", "ADD_W", "APPEND", "ARGUMENTS", "ARRAY", "ARRAY_EQUALS", "ARRAY_EXTEND", "ARRAY_EXTEND_ALL", "ARRAY_NUMBER", "ARRAY_STRING", "ASIN", "ATAN", "AVERAGE", "BASE64_DECODE", "BASE64_ENCODE", "BEEP", "BOOLEAN_XOR", "BUILTINS", "BUTTON_IMAGE", "CALL", "CALL_INFO", "CBRT", "CENTER", "CGI_CONTENTS", "CGI_GET", "CGI_HEADER", "CGI_POST", "CGI_POST_HASH", "CHR", "CLEAR", "CLIPBOARD_GET", "CLIPBOARD_SET", "CLOSING", "COLOR_CHOOSER", "COMPONENT", "COMPONENT_INFO", "COMPONENT_TYPE", "COMPONENTS", "CONFIG", "CONFIRM", "COPY", "COPY_RESOURCE", "COS", "COSH", "COUNT", "CR", "CRLF", "CUSTOM_DIALOG", "CUSTOM_DIALOG_CLOSE", "CWD", "DATABASE", "DATABASE_CONNECTED", "DATABASE_METADATA", "DATE", "DATETIME", "DAY", "DAYS_OF_MONTH", "DELAY", "DELETE", "DIFF", "DIR", "DIRECTORY", "DISABLED", "DO", "DRAW_ARC", "DRAW_GET_PIXEL", "DRAW_LINE", "DRAW_OVAL", "DRAW_POLYGON", "DRAW_POLYLINE", "DRAW_READ", "DRAW_RECT", "DRAW_ROUND_RECT", "DRAW_SET_PIXEL", "DRAW_STRING", "DRAW_WIDTH", "DRAW_WRITE_BMP", "DRAW_WRITE_JPG", "DRAW_WRITE_PNG", "EACH", "EMPTY", "ENDSWITH", "ENV", "EQUALS", "EVAL", "EVENT", "EVENT_FOCUS", "EVENT_FRAME", "EVENT_KEYBOARD_FRAME", "EVENT_MOUSE", "EVENT_MOUSE_FRAME", "EXIT", "EXP", "FILL_ARC", "FILL_OVAL", "FILL_POLYGON", "FILL_RECT", "FILL_ROUND_RECT", "FIRST", "FONTS", "FORMAT_DATE", "FORMAT_DATETIME", "FORMAT_DIFF", "FORMAT_TIME", "FRAME", "FRAME_CLOSE", "FRAME_IMAGE", "FRAME_LOCATION", "FRAME_TOP", "FROM_BIN", "FROM_HEX", "FROM_OCT", "GET", "GRID_ADD", "GRID_CLEAR", "GRID_REMOVE", "GUI", "HASH", "HELP", "HIDE", "HOUR", "HTTP_DELETE", "HTTP_GET", "HTTP_GET_FILE", "HTTP_HEAD", "HTTP_POST", "HTTP_POST_OVERRIDE", "HTTP_PUT", "HTTP_RESPONSE_OK", "IN", "INDEX", "INET_ADDRESS", "INET_ADDRESS_LOCAL", "INFO", "INPUT", "INTEGER", "INTEGER_GCD", "INTERACTIVE", "IS", "IS_ARRAY_AND_OF", "IS_ARRAY_OF", "IS_ARRAY_OF_COMPONENT_OF", "IS_LEAP_YEAR", "IS_RECT_ARRAY", "IS_RECT_ARRAY_OF", "IS_UPDATE_AVAILABLE", "ISALNUM", "ISALPHA", "ISDIGIT", "ISLOWER", "ISUPPER", "JOIN", "KEYS", "KEYWORDS", "LAST", "LEFT", "LEFT_SHIFT", "LEN", "LF", "LOAD", "LOAD_FILE_OR_RESOURCE", "LOAD_MODULE", "LOAD_RESOURCE", "LOG", "LOG10", "LOGIN_DIALOG", "LOWER", "MATCHES", "MAX", "MD5", "MD5_FILE", "MENUBAR", "MESSAGE", "MIN", "MINUTE", "MKDIR", "MODULES", "MONTH", "NEWLINE", "NUMBER", "NUMBER_AND", "NUMBER_BOOLEAN", "NUMBER_GROUP", "NUMBER_NOT", "NUMBER_OR", "NUMBER_SCALE", "NUMBER_TO_BYTE", "NUMBER_TO_BYTE_ARRAY", "NUMBER_XOR", "OPEN", "ORD", "PANEL_ADD", "PANEL_CLEAR", "PANEL_DIALOG", "PANEL_REMOVE", "PARAM", "PARSE_ARRAY", "PARSE_HASH", "PARSE_STRING", "PART", "PARTS", "PASSWORD", "PLAY_SOUND", "POP", "POPUP_COMPONENT", "POPUP_HIDE", "POPUP_SHOW", "PRINT", "PRINTER", "PRINTLN", "PROPERTIES_READ", "PROPERTIES_WRITE", "PUSH", "PUTS", "QUERY", "QUOTE", "RADIO_GROUP", "RANDOM", "RANDOM_STRING", "RANGE", "READ", "READ_BYTE", "RECT_ARRAY_SIZE", "RECT_ARRAY_SIZE_OF", "REMOVE", "REMOVE_E", "REMOVE_N", "REMOVE_S", "REMOVE_W", "RENAME", "REPLACE", "REQUIRE", "RESET", "RESIZABLE", "REST", "REVERSE", "RIGHT", "RIGHT_SHIFT", "ROUND", "RUNTIME_VERSION", "SAVE", "SCREEN", "SECOND", "SEPARATOR", "SET", "SHA1", "SHA1_FILE", "SHA256", "SHA256_FILE", "SHA384", "SHA384_FILE", "SHA512", "SHA512_FILE", "SHOW", "SHUFFLE", "SIN", "SINGKONG", "SINGKONG_INTERPRETER", "SINH", "SIZE", "SLICE", "SORT_ARRAY", "SORT_BOOLEAN", "SORT_DATE", "SORT_HASH", "SORT_NUMBER", "SORT_STRING", "SPLIT", "SQRT", "START", "START_TIMER", "STARTSWITH", "STAT", "STATUSBAR", "STDIN", "STOP", "STOP_TIMER", "STRING", "STRING_FROM_BYTE_ARRAY", "STRING_TO_BYTE_ARRAY", "SUM", "SYSTEM", "TAB", "TAB_ADD", "TAB_CLEAR", "TAB_REMOVE", "TABLE_ADD", "TABLE_BOTTOM", "TABLE_CENTER", "TABLE_COLUMN", "TABLE_COLUMN_COUNT", "TABLE_COLUMN_INFO", "TABLE_COLUMN_NAME", "TABLE_GET_VALUE", "TABLE_LEFT", "TABLE_MIDDLE", "TABLE_PRINT", "TABLE_REMOVE", "TABLE_RIGHT", "TABLE_ROW_COUNT", "TABLE_SCROLL", "TABLE_SET_VALUE", "TABLE_TOP", "TAN", "TANH", "TEMP_FILE", "THREAD", "THREAD_ALIVE", "THREAD_JOIN", "TIMER", "TIMER_RUNNING", "TITLE", "TO_BIN", "TO_DEGREES", "TO_HEX", "TO_OCT", "TO_RADIANS", "TRIM", "TYPE", "TYPES", "UNSIGNED_RIGHT_SHIFT", "UPPER", "URL_DECODE", "URL_ENCODE", "USER", "USERHOME", "VALUES", "VARIABLES", "WAIT", "WORDS_EN", "WORDS_ID", "WRITE", "WRITE_BYTE", "X_BASE64_DECODE_FILE", "X_BASE64_ENCODE_FILE", "X_EDIT_PRINT", "YEAR"] > len(builtins()) 344 Note: a built-in function may be disabled when the interpreter is run. In such case, an error will be returned when the function is called. To get basic information of a built-in function, please enter its name in interactive evaluator, without ( and ). For example: > random built-in function: random: returns random number (between 0 inclusive and 1 exclusive), random number between min and max (both inclusive), random element in ARRAY, random key in HASH arguments: 0, 1 (ARRAY or HASH), 2: (NUMBER and NUMBER) return value: To get a list of built-in modules, please call modules() > modules() ["csv", "db_util", "json", "rect_array_util", "set_util", "ui_calendar", "ui_util", "util", "web"] To load a built-in module, please use built-in function load_module, for example: > load_module("csv") > load_module("db_util") > load_module("json") > load_module("rect_array_util") > load_module("set_util") > load_module("ui_calendar") > load_module("ui_util") > load_module("util") > load_module("web") Note: reusing function name from built-in modules as identifier is allowed: > var len = "hello, world" ERROR: [line: 1] len is a built-in function > load_module("csv") > csv_from_string [documentation string] [csv] function: csv_from_string: returns ARRAY from CSV STRING (with separator). arguments: 2: STRING (separator) and STRING (CSV) return value: ARRAY or NULL (error) > var csv_from_string = "hello, world" > csv_from_string "hello, world" Functions in module: csv: ["CSV_FROM_STRING", "CSV_FROM_STRING_DEFAULT", "CSV_FUNCTIONS", "CSV_TO_STRING", "CSV_TO_STRING_DEFAULT"] Functions in module: db_util: ["CREATE_FIELD_FROM_ARRAY", "DB_CONNECT", "DB_CONNECT_EMBED", "DB_CONNECT_EMBED_", "DB_CONNECT_EMBED_USER", "DB_CREATE_TABLE", "DB_CREATE_TABLE_", "DB_CREATE_TABLE_DERBY", "DB_CREATE_TABLE_DERBY_", "DB_CREATE_TABLE_EMBED", "DB_CREATE_TABLE_EMBED_", "DB_CREATE_TABLE_POSTGRESQL", "DB_CREATE_TABLE_POSTGRESQL_", "DB_DELETE", "DB_DELETE_", "DB_DRIVER", "DB_INSERT", "DB_INSERT_", "DB_LAST", "DB_LAST_DERBY", "DB_LAST_EMBED", "DB_LAST_POSTGRESQL", "DB_QUERY_SIMPLE", "DB_QUERY_SINGLE", "DB_RUN_QUERY", "DB_SELECT", "DB_SELECT_", "DB_SELECT_ALL", "DB_SELECT_ALL_", "DB_SELECT_DERBY", "DB_SELECT_DERBY_", "DB_SELECT_EMBED", "DB_SELECT_EMBED_", "DB_SELECT_POSTGRESQL", "DB_SELECT_POSTGRESQL_", "DB_UPDATE", "DB_UPDATE_", "QUERY_RESULT"] Functions in module: json: ["JSON_PARSE", "JSON_PARSE_", "JSON_STRING"] Functions in module: rect_array_util: ["ADD_RECT_ARRAY_OF", "ADD_RECT_ARRAY_OF_NUMBER", "CROSS_PRODUCT_3D_RECT_ARRAY_COLUMN_OF", "CROSS_PRODUCT_3D_RECT_ARRAY_COLUMN_OF_NUMBER", "CROSS_PRODUCT_3D_RECT_ARRAY_ROW_OF", "CROSS_PRODUCT_3D_RECT_ARRAY_ROW_OF_NUMBER", "DETERMINANT_RECT_ARRAY_OF", "DETERMINANT_RECT_ARRAY_OF_NUMBER", "INVERSE_RECT_ARRAY_OF", "INVERSE_RECT_ARRAY_OF_NUMBER", "IS_CONSTANT_RECT_ARRAY_OF", "IS_CONSTANT_RECT_ARRAY_OF_NUMBER", "IS_DIAGONAL_RECT_ARRAY_OF", "IS_DIAGONAL_RECT_ARRAY_OF_NUMBER", "IS_IDENTITY_RECT_ARRAY_OF_NUMBER", "IS_LOWER_TRIANGULAR_RECT_ARRAY_OF", "IS_LOWER_TRIANGULAR_RECT_ARRAY_OF_NUMBER", "IS_RECT_ARRAY_COLUMN_OF", "IS_RECT_ARRAY_COLUMN_OF_NUMBER", "IS_RECT_ARRAY_ROW_OF", "IS_RECT_ARRAY_ROW_OF_NUMBER", "IS_SQUARE_RECT_ARRAY_OF", "IS_SQUARE_RECT_ARRAY_OF_NUMBER", "IS_SYMMETRIC_RECT_ARRAY_OF", "IS_SYMMETRIC_RECT_ARRAY_OF_NUMBER", "IS_UPPER_TRIANGULAR_RECT_ARRAY_OF", "IS_UPPER_TRIANGULAR_RECT_ARRAY_OF_NUMBER", "IS_ZERO_RECT_ARRAY_OF", "IS_ZERO_RECT_ARRAY_OF_NUMBER", "MINOR_RECT_ARRAY_OF", "MINOR_RECT_ARRAY_OF_NUMBER", "MUL_RECT_ARRAY_OF", "MUL_RECT_ARRAY_OF_NUMBER", "MUL_SCALAR_RECT_ARRAY_OF", "MUL_SCALAR_RECT_ARRAY_OF_NUMBER", "RECT_ARRAY_NOT_ROW_COL", "SUB_RECT_ARRAY_OF", "SUB_RECT_ARRAY_OF_NUMBER", "TRACE_RECT_ARRAY_OF", "TRACE_RECT_ARRAY_OF_NUMBER", "TRANSPOSE_RECT_ARRAY_OF", "TRANSPOSE_RECT_ARRAY_OF_NUMBER"] Functions in module: set_util: ["CREATE_RELATION_FROM_ARRAY", "CREATE_SET_FROM_ARRAY", "GET_RELATION_PART", "INVERSE_BIJECTIVE_FUNCTION_SET", "IS_ANTISYMMETRIC_RELATION_SET", "IS_BIJECTIVE_FUNCTION_SET", "IS_FUNCTION_SET", "IS_INJECTIVE_FUNCTION_SET", "IS_REFLEXIVE_RELATION_SET", "IS_RELATION", "IS_RELATION_SET", "IS_SAME_SET", "IS_SUB_SET", "IS_SURJECTIVE_FUNCTION_SET", "IS_SYMMETRIC_RELATION_SET", "IS_TRANSITIVE_RELATION_SET", "SET_DIFF", "SET_INTERSECTION", "SET_POWER", "SET_PRODUCT", "SET_UNION"] Functions in module: ui_calendar: ["CREATE_CALENDAR", "CREATE_CALENDAR_BASIC", "CREATE_CALENDAR_BASIC_COMPACT", "CREATE_CALENDAR_SIMPLE", "CREATE_CALENDAR_SIMPLE_COMPACT", "CREATE_DATE_PICKER", "CREATE_DATE_PICKER_"] Functions in module: ui_util: ["TABLE_ADD_FILL", "TABLE_ADD_ROW_FILL", "TABLE_FILL", "TABLE_GET_ARRAY_", "TABLE_GET_ARRAY_NUMBER", "TABLE_GET_ARRAY_STRING", "TABLE_TO_HTML", "TABLE_TO_TEXT"] Functions in module: util: ["ARRAY_COPY", "ARRAY_DIFF", "BINOMIAL_COEFFICIENT", "BINOMIAL_DISTRIBUTION", "FACTORIAL", "GEOMETRIC_DISTRIBUTION_FAILURE", "GEOMETRIC_DISTRIBUTION_SUCCESS", "HASH_COPY", "MEAN", "MEDIAN", "MODE", "NUMBER_GROUP_C_P", "NUMBER_GROUP_P_C", "NUMBER_GROUP_S_C", "NUMBER_GROUP_S_P", "POISSON_DISTRIBUTION", "RANGE_", "SIMPLE_FILE_DECRYPT", "SIMPLE_FILE_ENCRYPT", "SIMPLE_STRING_DECRYPT", "SIMPLE_STRING_ENCRYPT", "SORT_RECT_ARRAY_OF_NUMBER_BY_INDEX", "STANDARD_DEVIATION", "STANDARD_DEVIATION_", "STANDARD_DEVIATION_SAMPLE", "VARIANCE", "VARIANCE_", "VARIANCE_SAMPLE", "_NON_EMPTY_ARRAY_OF_NUMBER"] Functions in module: web: ["HEADER_LOCATION", "HEADER_SESSION", "HEADER_SESSION_", "HEADER_SESSION_DELETE", "HEADER_SESSION_DELETE_", "HEADER_SESSION_SECURE", "REQUEST_METHOD", "SESSION_FILE_DELETE", "SESSION_FILE_GET", "SESSION_FILE_GET_", "SESSION_FILE_SET", "SESSION_NEW"] ----------------------------------------------------------------------------- 6. If condition Syntax: if (condition) {consequences} else {alternatives} condition: BOOLEAN operators: &(and) |(or) Example: var name = "singkong" if (name == "singkong") { print(name) } Example (output: idle): var t = thread(fn() {delay(500)}) thread_join(t) if (thread_alive(t)) { println("busy") } else { println("idle") } Note: HASH (key: condition, value: FUNCTION) may also be used Example: var test = { "hello": fn() { println("Hello") }, "world": fn() { println("World") }, } var x = lower(input("Enter: ")) if (!in(keys(test), x)) { println("Error") } else { test[x]() } ----------------------------------------------------------------------------- 7. Repeat loop repeat { statements } To exit from loop, use return Example: var i = 0 repeat { print(" " + i) var i = i + 1 if (i > 5) { return i } } Please also use do and each functions for simple repetitions: - do: calls a function a number of times, optionally with arguments - each: for each element in an ARRAY, calls a function a number of times (with arguments). Specified function must accept two arguments: the element and index (zero based). Examples: do(5, print, "Hello World ") do(5, println, "hello", "world") var a = ["Hello", "World"] each(a, fn(e, i) { print(i, ": ", e, newline()) }) var a = {"Hello": "World"} each(keys(a), fn(e, i) { print(e, ": ", a[e], newline()) }) var a = "Hello World" each(array(a), fn(e, i) { println(e) }) var a = ["Hello", "World"] each(a, println) ----------------------------------------------------------------------------- 8. GUI application development Singkong supports simple graphical user interface application development. To keep it as simple as possible: - There is only one frame for each Singkong program. No other frames can be created (using Singkong code). To show common dialogs, please use related built-in functions (confirm, directory, input, open, message, password, save, login_dialog, color_chooser). To create custom panel or grid dialogs, please use built-in function panel_dialog (modal) or custom_dialog (modal, modeless). To create more advanced dialogs, please use Java method. To set frame title, please use built-in function title. To set frame size, please use built-in function size (this might not work on some system configuration and will be ignored). To change always on top state, please use built-in function frame_top. To move frame to a new location or to the center of the screen, please use built-in function frame_location. To set frame's icon image, please use built-in function frame_image. To get frame properties (width, height, title, resizable, always on top, x, y), please use built-in function frame. To reset frame (remove all COMPONENTs, reset frame title and size to default values, stop all timers, disable frame closing confirmation, reset status bar, reset menu bar, reset always on top state, hide all popups), please use built-in function reset. To programmatically close frame, please use built-in function frame_close. - Every user interface component is a COMPONENT in Singkong. - To create a user interface component, please use built-in function component. This function accepts two arguments: component type and name, both as string. Component type must be a valid component type, and name will be used both as name and important properties. For example, to create a button, "button" must be passed as component type and name is used as label of that button. Different component may use name for different purposes. For example: when creating a table, name (comma-separated string) is used as column names. This function also accepts optional argument BOOLEAN: if true, created component will be read-only (default: false). However, this only applicable when creating edit, mask, password, table, or text. - Component name interpretation: - barchart: (none) - button: label - checkbox: label - combobox: items (comma-separated string) - date: format - draw: width (integer; default 100), height (integer; default 100) - edit: text - grid: tab title - image: file name - label: label - mask: format (#: number, U: upper case, L: lower case, A: character or number, ?: character, *: anything, H: hex character; for more information: javax.swing.text.MaskFormatter) - panel: tab title - password: text - piechart: (none) - progress: (none) - radio: label - spin: value (integer), minimum (integer), maximum (integer), step size (integer) invalid value: default values will be used - tab: (none) - table: column names (comma-separated string) - text: text - view: HTML code - Popup menu and keyboard shortcut for: edit, text, view: - opening popup menu using mouse: right click or control click (depending on the operating system) - Select All (A), Copy (C), Cut (X), Paste (V), Undo (Z), Redo (Y) - Using Ctrl or Command key (depending on the operating system) - To get a list of supported component types, built-in function components can be used. Currently, this function returns: ["barchart", "button", "checkbox", "combobox", "date", "draw", "edit", "grid", "image", "label", "mask", "panel", "password", "piechart", "progress", "radio", "spin", "tab", "table", "text", "view"] - To get type of a COMPONENT, please use built-in function component_type. This function returns component type as STRING (one of values returned by components function, as above). - To get x (screen), y (screen), x, y, width, height, name, and read-only information of a COMPONENT, please use built-in function component_info. - To add a COMPONENT or an ARRAY of COMPONENT to frame, please use one of the following built-in functions: add, add_e, add_n, add_s, add_w (which adds a COMPONENT or an ARRAY of COMPONENT to specific region (center, east, north, south, west) of frame. Existing components in that region will be removed, prior to addition). reset() var b1 = component("button", "Button 1") var b2 = component("button", "Button 2") var b3 = component("button", "Button 3") add([b1, b2]) add_s(b3) show() - To remove COMPONENTs from specific region (center, east, north, south, west) of frame, please use one of the following built-in functions: remove, remove_e, remove_n, remove_s, remove_w. To remove all COMPONENTs from frame, please use built-in function clear. - To use absolute positioning, please use panel COMPONENT, then add the panel to frame, panel, or grid: - To add a COMPONENT to a panel, please use built-in function panel_add. Please note that COMPONENT will not be resized when the container is resized. reset() var p = component("panel", "") var b1 = component("button", "Button 1") var b2 = component("button", "Button 2") panel_add(p, b1, 10, 10, 200, 50) panel_add(p, b2, 10, 70, 200, 30) add(p) show() - To remove a COMPONENT from a panel, please use built-in function panel_remove. - To remove all COMPONENTs from a panel, please use built-in function panel_clear. - To use grid based layout, please use grid COMPONENT, then add the grid to frame, panel, or grid: - To add a COMPONENT to a grid, please use built-in function grid_add. Please note that COMPONENT will be resized when the container is resized, based on the constraints. reset() var g = component("grid", "") var b1 = component("button", "Button 1") var b2 = component("button", "Button 2") var b3 = component("button", "Button 3") var b4 = component("button", "Button 4") var b5 = component("button", "Button 5") grid_add(g, b1, 0, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) grid_add(g, b2, 1, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) grid_add(g, b3, 0, 1, 2, 1, 1.0, 0.5, 3, 0, 5, 5, 5, 5) grid_add(g, b4, 0, 2, 1, 1, 0.5, 0.5, 0, 1) grid_add(g, b5, 1, 2, 1, 1, 0.5, 0.5, 0, 2) add(g) show() - To remove a COMPONENT from a grid, please use built-in function grid_remove. - To remove all COMPONENTs from a grid, please use built-in function grid_clear - To enable or disable frame closing confirmation, please use built-in function closing. - To set whether frame is resizable, please use built-in function resizable. - To use tabbed pane, please use tab COMPONENT, then add the tab to frame, a panel, or a grid: - To add a panel or a grid to a tab, please use built-in function tab_add. - To remove a panel or a grid from a tab, please use built-in function tab_remove. - To remove all panels or grids from a tab, please use built-in function tab_clear. reset() var e = component("edit", "") var c = component("checkbox", "Singkong?") var p = component("panel", "Panel") panel_add(p, e, 10, 10, 200, 50) panel_add(p, c, 10, 70, 100, 30) var g = component("grid", "Grid") var b1 = component("button", "Button 1") var b2 = component("button", "Button 2") grid_add(g, b1, 0, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) grid_add(g, b2, 1, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) var t = component("tab", "") tab_add(t, p) tab_add(t, g) add(t) show() - To create custom panel or grid dialog, please use built-in function panel_dialog (modal) or custom_dialog (modal, modeless): - using panel: reset() var e = component("edit", "") var c = component("checkbox", "Singkong?") var p = component("panel", "") panel_add(p, e, 10, 10, 200, 50) panel_add(p, c, 10, 70, 100, 30) var r = panel_dialog(p, "Form", 300, 200) if (r == "OK") { message(get(e, "contents")) message(get(c, "active")) } else { message("Cancel") } - using grid: reset() var e = component("edit", "") var c = component("checkbox", "Singkong?") var g = component("grid", "") grid_add(g, e, 0, 0, 1, 1, 1, 1, 3, 0, 5, 5, 5, 5) grid_add(g, c, 0, 1, 1, 1, 0, 0, 3, 0, 5, 5, 5, 5) var r = panel_dialog(g, "Form", 300, 200) if (r == "OK") { message(get(e, "contents")) message(get(c, "active")) } else { message("Cancel") } - modeless dialog using custom_dialog: reset() add_n(component("text", "Edit me while dialog is open")) show() var e = component("edit", "") var c = component("checkbox", "Singkong?") var b = component("button", "Close") var g = component("grid", "") grid_add(g, e, 0, 0, 1, 1, 1, 1, 3, 0, 5, 5, 5, 5) grid_add(g, c, 0, 1, 1, 1, 0, 0, 3, 0, 5, 5, 5, 5) grid_add(g, b, 0, 2, 1, 1, 0, 0, 0, 2) event(c, fn() { var ec = get(e, "contents") var cc = get(c, "active") var nc = ec + lf() + cc config(e, "contents", nc) }) var r = [] event(b, fn() { custom_dialog_close(last(r)) }) custom_dialog(g, "Form", 600, 400, false, r) - modal dialog using custom_dialog: reset() add_n(component("text", "Can't edit me while dialog is open")) show() var e = component("edit", "") var c = component("checkbox", "Singkong?") var b = component("button", "Close") var g = component("grid", "") grid_add(g, e, 0, 0, 1, 1, 1, 1, 3, 0, 5, 5, 5, 5) grid_add(g, c, 0, 1, 1, 1, 0, 0, 3, 0, 5, 5, 5, 5) grid_add(g, b, 0, 2, 1, 1, 0, 0, 0, 2) event(c, fn() { var ec = get(e, "contents") var cc = get(c, "active") var nc = ec + lf() + cc config(e, "contents", nc) }) var r = [] event(b, fn() { custom_dialog_close(last(r)) }) custom_dialog(g, "Form", 600, 400, true, r) - To configure a COMPONENT, please use built-in function config. This function accepts three arguments: COMPONENT, STRING (key), and (value). If optional fourth argument (BOOLEAN) is true (default: false) and (key = contents) and (type of COMPONENT is one of: edit/image/password/text/view) and (type of value is STRING) then read /resource/ in interpreter jar file. (please also read about deployment) Different component may have different keys, but common keys are: - border (all supported components, STRING (title)) - enabled (all components, BOOLEAN) - visible (all components, BOOLEAN) - focus (all components, BOOLEAN) - foreground/background (all components, STRING (color name or RGB value)) - font (all components, ARRAY [STRING (font name), NUMBER (0=plain, 1=bold, 2=italic, 3=bold and italic), NUMBER (size)]) - text (label of button/checkbox/label/radio, selected item of combobox, title of barchart/piechart, STRING) - active (checkbox/radio BOOLEAN, combobox/tab/table NUMBER (selected index), button NUMBER (mnemonic index), text NUMBER (column, applicable in grid)) - contents (contents of edit/mask/password/text/view STRING, file of image STRING, ARRAY of ARRAY (table), ARRAY (combobox), DATE/STRING (date), NUMBER (progress/spin), ARRAY of ARRAY (of [NUMBER (value), STRING (label), STRING (color name or RGB value)], barchart/piechart)) - margin (button ARRAY of NUMBER [top, left, bottom, right], applicable in grid) - align (button/label/mask/password/text, horizontal alignment, NUMBER(0=left, 1=center, 2=right, 3=leading, 4=trailing)) - border_painted (button, BOOLEAN) (configuration may be ignored if it is not applicable -- no errors will be returned). - To get a configuration of a COMPONENT configured using config, please use built-in function get. This function accepts two arguments: COMPONENT and STRING (key). - border (all supported components, STRING (title)) - enabled (all components, BOOLEAN) - visible (all components, BOOLEAN) - focus (all components, BOOLEAN) - foreground/background (all components, STRING (color name or RGB value)) - font (all components, ARRAY [STRING (font name), NUMBER (0=plain, 1=bold, 2=italic, 3=bold and italic), NUMBER (size)]) - text (label of button/checkbox/label/radio, selected item of combobox, STRING) - active (checkbox/radio BOOLEAN, combobox/tab/table NUMBER, button NUMBER, text NUMBER) - contents (contents of edit/mask/password/text/view STRING, file of image STRING, ARRAY of ARRAY (of STRING, table), ARRAY (of STRING, combobox), DATE (date), NUMBER (progress/spin)) - margin (button ARRAY of NUMBER [top, left, bottom, right]) - align (button/label/mask/password/text, horizontal alignment, NUMBER(0=left, 1=center, 2=right, 3=leading, 4=trailing)) - border_painted (button, BOOLEAN) (configuration may be ignored if it is not applicable and NULL will be returned). - To use resources for contents of edit/image/password/text/view: - put all resource files in /resource directory in interpreter jar file for example: /resource/file.png /resource/test.txt (please also read about deployment) - built-in function config examples: (1) image var c = component("image", "") config(c, "contents", "file.png") # from current working directory; add(c) show() var c = component("image", "") config(c, "contents", "file.png", true) # from /resource/file.png in interpreter jar file; add(c) show() (2) text var t = component("text", "") config(t, "contents", "test.txt") # text contains: test.txt; add(t) show() var t = component("text", "") config(t, "contents", "test.txt", true) # from /resource/test.txt in interpreter jar file; add(t) show() - table: to add rows or remove a row, please use built-in function table_add or table_remove. To scroll to specified row (starting from 0), please use built-in function table_scroll. - radio: to group an ARRAY of COMPONENT radio buttons (mutual-exclusion set), please use built-in function radio_group. - button: to set button's icons, please use built-in function button_image. - The following COMPONENTs have default event: - button: pressed - combobox: selected item changed - checkbox: checked/unchecked - radio: selected/deselected - tab/table: selected item changed - date/edit/password/spin/text: contents changed Event handlers for these events can be registered using built-in function event. This function accepts two arguments: COMPONENT and FUNCTION. Registered FUNCTION will be called whenever the default event for specified COMPONENT should be handled. Please note that specified FUNCTION must not require any arguments. - Mouse: - Mouse and mouse motion events: event handler for these events can be registered using built-in function event_mouse. This function accepts two arguments: COMPONENT and FUNCTION. Registered FUNCTION will be called whenever mouse event for specified COMPONENT should be handled. Please note that specified FUNCTION must require one argument (an ARRAY [type (STRING: CLICKED, ENTERED, EXITED, PRESSED, RELEASED, MOVED, DRAGGED), x, y, click count]). reset() var b = component("button", "mouse") var t = component("table", "TYPE, X, Y, CLICK COUNT", true) var f = fn(x) { table_add(t, [x]) } event_mouse(b, f) add(t) add_s(b) show() - Mouse and mouse motion events for frame: event handler for these events can be registered using built-in function event_mouse_frame. This function accepts one argument: FUNCTION. reset() var t = component("table", "TYPE, X, Y, CLICK COUNT", true) var f = fn(x) { table_add(t, [x]) } event_mouse_frame(f) add_w(t) show() - Keyboard events for frame: event handler for these events can be registered using built-in function event_keyboard_frame. This function accepts one argument: FUNCTION. Registered FUNCTION will be called whenever keyboard event for frame should be handled. Please note that specified FUNCTION must require one argument (an ARRAY [type (STRING: TYPED, PRESSED, RELEASED), character (STRING, if applicable), code (NUMBER), key text, is action, modifiers text, key location (STRING: STANDARD, LEFT, RIGHT, NUMPAD, UNKNOWN), is alt down, is alt graph down, is control down, is meta down, is shift down]). reset() var t = component("table", "TYPE, CHAR, CODE, KEY, ACTION, MODIFIER, LOCATION, ALT, ALT GRAPH, CONTROL, META, SHIFT", true) var f = fn(x) { table_add(t, [x]) } event_keyboard_frame(f) add(t) show() - Events for frame: event handler for these events can be registered using built-in function event_frame. This function accepts one argument: FUNCTION. Registered FUNCTION will be called whenever event for frame should be handled. Please note that specified FUNCTION must require one argument (an ARRAY [type (STRING: ACTIVATED, CLOSED, DEACTIVATED, DEICONIFIED, GAINEDFOCUS, ICONIFIED, LOSTFOCUS, OPENED, STATECHANGED, RESIZED, MOVED), width, height, x, y]). (please also read about built-in function closing) reset() var t = component("table", "TYPE, WIDTH, HEIGHT, X, Y", true) var f = fn(x) { table_add(t, [x]) } event_frame(f) add(t) closing("Are you sure you want to quit this application?", "Please confirm") show() - Focus events: event handler for these events can be registered using built-in function event_focus. This function accepts two arguments: COMPONENT and FUNCTION. Registered FUNCTION will be called whenever focus event for specified COMPONENT should be handled. Please note that specified FUNCTION must require one argument (an ARRAY [type (STRING: GAINED, LOST), x, y]). reset() var i = component("label", "Focus event: text") var t = component("table", "TYPE, X, Y", true) var x = component("text", "Click") event_focus(x, fn(x) { table_add(t, [x]) }) add_n(i) add(t) add_s(x) show() - Popup: - Not to be confused with popup menu. - To show a popup, please use built-in function popup_show. - To make a popup modeless (default: modal), please pass false as optional seventh argument of popup_show or popup_component. - Using panel reset() var t = component("text", "") var b = component("button", "OK") var p = component("panel", "") panel_add(p, t, 10, 10, 100, 30) panel_add(p, b, 10, 50, 80, 30) var r = [-1] var c = component("button", "popup") event(c, fn() { var x = popup_show(p, 120, 100, 50, 50, false) set(r, 0, x) }) add_s(c) show() - Using grid reset() var t = component("text", "") var b = component("button", "OK") var g = component("grid", "") grid_add(g, t, 0, 0, 1, 1, 1, 1, 3, 0) grid_add(g, b, 0, 1, 1, 1, 1, 1, 1, 0) var r = [-1] var c = component("button", "popup") event(c, fn() { var x = popup_show(g, 120, 100, 50, 50, false) set(r, 0, x) }) add_s(c) show() - Using grid and relative popup position reset() var t = component("text", "") var b = component("button", "OK") var g = component("grid", "") grid_add(g, t, 0, 0, 1, 1, 1, 1, 3, 0) grid_add(g, b, 0, 1, 1, 1, 1, 1, 1, 0) var r = [-1] var c = component("button", "popup") event(c, fn() { var x = popup_show(g, 120, 100, 50, 50, true) set(r, 0, x) }) add_s(c) show() - To show a popup at a COMPONENT, please use built-in function popup_component. - Using grid and focus event handler reset() var t = component("text", "Focus on combobox") var c = component("combobox", "") var g = component("grid", "") config(g, "background", "gray") var r = [-1] event_focus(c, fn(e) { if (e[0] == "GAINED") { var i = component_info(c) var x = popup_component(g, c, i[4], 300, 0, i[5]) set(r, 0, x) } }) add_n([t, c]) show() - To hide a popup, please use built-in function popup_hide. - Using grid and button inside the popup reset() var t = component("text", "") var b = component("button", "Close") var g = component("grid", "") grid_add(g, t, 0, 0, 1, 1, 1, 1, 3, 0) grid_add(g, b, 0, 1, 1, 1, 1, 1, 1, 0) var r = [-1] var c = component("button", "popup") event(c, fn() { var x = popup_show(g, 120, 100, 50, 50, true) set(r, 0, x) }) add_s(c) show() event(b, fn() { popup_hide(r[0]) }) - Using grid and focus event handler reset() var t = component("text", "Focus on combobox") var c = component("combobox", "") var g = component("grid", "") config(g, "background", "gray") var r = [-1] event_focus(c, fn(e) { if (e[0] == "GAINED") { var i = component_info(c) var x = popup_component(g, c, i[4], 300, 0, i[5]) set(r, 0, x) } else { popup_hide(r[0]) } }) add_n([t, c]) show() - Clipboard: to set clipboard contents, please use built-in function clipboard_set. To get clipboard contents, built-in function clipboard_get can be used. - Printing: simple printing can be done using print dialog. To show a print dialog, please use built-in function printer. Basic settings such as font name, font size, left/top margin are supported. - To print a table, please use built-in function table_print - To print an edit, please use built-in function x_edit_print. This function requires minimum Java version: 6. - To get the size of the screen, please use built-in function screen. - To calls a FUNCTION every specified delay, please use built-in function timer. This function accepts two arguments: NUMBER (delay in milliseconds) and FUNCTION. - To stop all timers, please use built-in function stop. - To stop specific timer, please use built-in function stop_timer. - To start all timers, please use built-in function start. - To start specific timer, please use built-in function start_timer. - To get whether specific timer is running, please use built-in function timer_running. - Long running process: to display please wait message and call FUNCTION or BUILTIN in another thread, please use built-in function wait. This function accepts two arguments (at least): COMPONENT (label) and (FUNCTION or BUILTIN). If FUNCTION or BUILTIN requires arguments, please also pass the arguments to wait function. (please also read about: Working with threads) Example: using wait function: reset() var l1 = component("label", "please wait...") config(l1, "font", ["monospaced", 1, 20]) var l2 = component("label", "busy...") config(l2, "font", ["monospaced", 1, 20]) var b1 = component("button", "Delay") var b2 = component("button", "Busy") add_s([b1, b2]) var busy = fn() { delay(2000) } event(b1, fn() { wait(l1, delay, 1000) }) event(b2, fn() { wait(l2, busy) }) show() Example: using thread, updating user interface components: var p = [0, "Connecting..."] var f = fn() { delay(random(1000, 1500)) set(p, 0, random(30, 35)) set(p, 1, "Downloading...") delay(random(2000, 2500)) set(p, 0, random(70, 75)) set(p, 1, "Processing...") delay(random(3000, 3500)) set(p, 0, 100) set(p, 1, "Done") delay(1000) } var t = thread(f, p) var s = number(@) reset() var x = component("progress", "") add_s(x) var c = fn() { if (thread_alive(t)) { var m = number(@) - s statusbar(0, m + " ms", true) statusbar(1, p[1], true) statusbar(2, p[0] + "%", true) config(x, "contents", p[0]) } else { stop() } } timer(random(100, 500), c) show() - To get list of available fonts, please use built-in function fonts. - Date: - When creating a date COMPONENT, name is used as format pattern. If it is not set, default value of "yyyy-MM-dd" is used. If time is needed, in addition to default format, "yyyy-MM-dd HH:mm:ss" may be used. If day and month names are preferred, "EEE, yyyy-MMM-dd" or "EEEE, yyyy-MMMM-dd" may be used. Please refer to Java SimpleDateFormat documentation for supported format patterns. - To configure a date COMPONENT, a DATE or a STRING representation of a DATE can be used: - If STRING representation is used: please use built-in function parts to convert parts of a DATE as STRING. For example: var d = component("date","") config(d, "contents", parts(@)) - If DATE is used: no conversion is needed. For example: var d = component("date","") config(d, "contents", @) - Get a configuration of a date COMPONENT: a DATE is returned - Setting the number of columns of a text component: - Configure: active - Applicable in grid - Example: reset() var g = component("grid", "") var t1 = component("text", "Text 1") config(t1, "active", 10) var t2 = component("text", "Text 2") grid_add(g, t1, 0, 0, 1, 1, 1, 1, 0, 0) grid_add(g, t2, 1, 0, 1, 1, 1, 1, 0, 0) add(g) show() - Sound: - To play a beep sound, please use built-in function beep. - To play a sound from file or resource, please use built-in function play_sound. - resource: put all supported resource files in /resource directory in interpreter jar file. For example: play_sound("sound.wav", true) # from /resource/sound.wav in interpreter jar file; (please also read about deployment) - file: play_sound("sound.wav", false) # from current working directory; - Status bar: - Number of sections: 8 (0 to 7) - To set status bar text and whether or not the text is enabled, for specific section, please use built-in function statusbar. each(range(0,8), fn(e, i) { statusbar(e, "Status: " + e, i%2 == 0) }) - Menu bar: - To set menu bar, please use built-in function menubar. - This function requires an ARRAY of: [STRING (menu), NUMBER (mnemonic index), ARRAY of [] (separator) or [STRING (item), NUMBER (mnemonic index), BOOLEAN (is enabled), FUNCTION]] - Example: File (Exit): var menu = [ ["File", 0, [ ["Exit", 1, true, fn(){}] ] ] ] reset() menubar(menu) show() - Example: File (Exit (disabled)): var menu = [ ["File", 0, [ ["Exit", 1, false, fn(){}] ] ] ] reset() menubar(menu) show() - Example: File (Open, , Save (disabled)): var menu = [ ["File", 0, [ ["Open", 0, true, fn(){}], [], ["Save", 0, false, fn(){}] ] ] ] reset() menubar(menu) show() - Example: File (Open (with action), , Save (disabled)): var menu = [ ["File", 0, [ ["Open", 0, true, fn(){open()}], [], ["Save", 0, false, fn(){}] ] ] ] reset() menubar(menu) show() - Example: dynamically setting menu: var file_open = fn() { message("File Open") } var file_save = fn() { message("File Save") } var help_about = fn() { message("Help About") } var user_login = fn() { message("User Login") } var a = [ ["File", 0, [ ["Open", 0, true, file_open], [], ["Save", 1, false, file_save] ] ], ["Help", 0, [ ["About", 2, true, help_about] ] ] ] var b = [ ["User", 0, [ ["Login", 0, true, user_login] ] ] ] reset() var btn_a = component("button", "A") var btn_b = component("button", "B") add_s([btn_a, btn_b]) event(btn_a, fn() { menubar(a) }) event(btn_b, fn() { menubar(b) }) show() - Drawing: built-in functions: draw_width draw_rect fill_rect draw_arc fill_arc draw_oval fill_oval draw_round_rect fill_round_rect draw_string draw_line draw_polygon fill_polygon draw_polyline draw_read draw_write_png draw_write_jpg draw_write_bmp draw_get_pixel draw_set_pixel example: reset() var dr = component("draw", "500, 500") add(dr) config(dr, "foreground", "black") config(dr, "background", "white") draw_width(dr, 2) draw_rect(dr, 10, 10, 100, 50) fill_rect(dr, 120, 10, 100, 50) draw_arc(dr, 10, 80, 100, 50, 90, 270) fill_arc(dr, 120, 80, 100, 50, 90, 270) draw_oval(dr, 10, 150, 100, 50) fill_oval(dr, 120, 150, 100, 50) draw_round_rect(dr, 10, 220, 100, 50, 30, 30) fill_round_rect(dr, 120, 220, 100, 50, 30, 30) config(dr, "font", ["monospaced", 1, 30]) draw_string(dr, "Singkong", 10, 320) draw_line(dr, 10, 340, 240, 340) draw_polygon(dr, [10, 50, 90], [400, 360, 400]) fill_polygon(dr, [120, 160, 200], [400, 360, 400]) draw_polyline(dr, [10, 50, 90], [480, 440, 480]) show() - Using ui_util module: - contents of table as ARRAY (skipping empty cells): table_get_array_, table_get_array_number, table_get_array_string - table to HTML: table_to_html - table fill and table to text: - code: load_module("ui_util") reset() var t = component("table", "A,B,C") table_left(t, 0) table_center(t, 1) table_right(t, 2) var e = component("edit", "") config(e, "font", ["monospaced", 0, 10]) table_add_fill(t, 0) var tt = table_to_text(t, [10, 20, 30]) config(e, "contents", tt) add([t, e]) println(tt) show() - output: ================================================================ | A | B | C | ================================================================ |0 | 0 | 0| ---------------------------------------------------------------- |0 | 0 | 0| ---------------------------------------------------------------- |0 | 0 | 0| ================================================================ - Calendar: - Using ui_calendar module: - Example: simple style load_module("ui_calendar") reset() var d = part(@) var g = create_calendar_simple(d[0], d[1]) add(g) show() - Example: simple style, compact buttons load_module("ui_calendar") reset() var d = part(@) var g = create_calendar_simple_compact(d[0], d[1]) add(g) show() - Example: basic style load_module("ui_calendar") reset() var d = part(@) var g = create_calendar_basic(d[0], d[1]) add(g) show() - Example: basic style, compact buttons load_module("ui_calendar") reset() var d = part(@) var g = create_calendar_basic_compact(d[0], d[1]) add(g) show() - Example: custom style, button handler load_module("ui_calendar") reset() var h = fn(d, s, dow) { message(d) message(s) message(dow) } var d = part(@) var data = { "handler": h, "prev_handler": h, "next_handler": h, "today_foreground": "blue", "weekend_days": [1], "weekend_foreground": "red", "prev_foreground": "gray", "next_foreground": "gray", "margin": [0, 0, 0, 0] } var g = create_calendar(d[0], d[1], data) add(g) show() - For more information, please read documentation of create_calendar. - Date picker (Using ui_calendar module) load_module("ui_calendar") reset() var p = part(@) var d = create_date_picker(p[0], p[1]) add_n(d[0]) show() ----------------------------------------------------------------------------- 9. Database application development Singkong supports simple database application development (SQL relational database management system). - Every valid connection to a database system is a DATABASE in Singkong. - To work with relational database without using SQL command directly, please use db_util module (new in Singkong version 6.1) - To connect to a database system, please use built-in function database. This function accepts four arguments: - STRING: database driver, Java class found in class path - STRING: database URL - STRING: user - STRING: password - Singkong.jar contains the following database drivers (compatible with Java 5.0 or later): - Apache Derby - org.apache.derby.jdbc.EmbeddedDriver - org.apache.derby.jdbc.ClientDriver - PostgreSQL - org.postgresql.Driver for complete database URLs and their options, please refer to Apache Derby or PostgreSQL JDBC documentation - Singkong.jar also contains Apache Derby Network Server (compatible with Java 5.0 or later) - To run the network server with custom security policy and built-in authentication (username: admin, password: admin), please create derby.policy and derby.properties files in current working directory: - derby.policy grant { permission java.io.FilePermission "${user.dir}${/}-", "read, write, delete"; permission java.lang.RuntimePermission "getFileStoreAttributes"; permission java.lang.RuntimePermission "createClassLoader"; permission java.lang.RuntimePermission "accessUserInformation"; permission java.util.PropertyPermission "derby.__serverStartedFromCmdLine", "read, write"; permission java.util.PropertyPermission "user.dir", "read"; permission java.net.SocketPermission "127.0.0.1:1527", "accept, connect, listen,resolve"; permission java.net.SocketPermission "127.0.0.1", "accept, resolve"; }; - derby.properties derby.connection.requireAuthentication=true derby.authentication.provider=BUILTIN derby.user.admin=admin - Singkong.jar java -cp Singkong.jar -Djava.security.manager -Djava.security.policy=derby.policy org.apache.derby.drda.NetworkServerControl start - To run the network server without security manager and without authentication (for testing only, please do not do that in production environment): java -cp Singkong.jar org.apache.derby.drda.NetworkServerControl start -noSecurityManager Please refer to Apache Derby documentation for more information - In order to use external database drivers from another jar file/directory, please run Singkong using the following command: java -cp Singkong.jar: com.noprianto.singkong.Singkong or java -cp Singkong.jar: com.noprianto.singkong.Singkong or java -cp Singkong.jar; com.noprianto.singkong.Singkong or java -cp Singkong.jar; com.noprianto.singkong.Singkong where is a jar file and is a directory containing database driver class. Please note that class path separator character is operating system dependent (: or ;). If the driver class file is located in current working directory, might be replaced with a ., for example: java -cp Singkong.jar:. com.noprianto.singkong.Singkong (please also read about deployment) - Database connection example: > var db = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") > db DATABASE (URL=jdbc:derby:test;create=true, user=, driver=org.apache.derby.jdbc.EmbeddedDriver) - To test whether a database connection is valid, please use built-in function database_connected (or check whether db is null). - Supported Singkong and Java type mapping: - BOOLEAN -> Java: boolean - DATE -> Java: java.sql.Timestamp or java.sql.Date (if error) - NULL -> Java: null (java.lang.Object, non-typed null) or String (if error) - NUMBER -> Java: int (if a NUMBER looks like an integer) or Java: java.math.BigDecimal - STRING -> Java: String - Unsupported Singkong data types in query: STRING representation Query result: - Database CLOB -> Singkong STRING, or STRING representation (if error) - Database Date or Timestamp -> Singkong DATE - Database Integer, Bigint, or Decimal -> Singkong NUMBER - Database serial, bigserial, or numeric -> Singkong NUMBER - Database null -> Singkong NULL - Database boolean or bool -> Singkong BOOLEAN - Other types -> Singkong STRING or STRING representation - To run an ARRAY of SQL queries, please use built-in function query. This function accepts two arguments: DATABASE and ARRAY of ARRAY. Each element in the specified ARRAY must be an ARRAY of two elements: STRING (SQL command and ARRAY of arguments). SQL queries are run in transaction, which will be committed if no errors occurred. If optional third argument (BOOLEAN) is true (default: false), include column labels and types in the result. > var d = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") > var q = [ ["create table test(a integer, b integer)", []] ] > var r = query(d, q) > r [0] > var q = [ ["insert into test(a, b) values(?, ?)", [1, 1]] ] > var r = query(d, q) > r [1] > var q = [ ["insert into test(a, b) values(?, ?)", [2, 2]] ] > var r = query(d, q) > r [1] > var q = [ ["select * from test", []] ] > var r = query(d, q) > r [[[1, 1], [2, 2]]] > var r = query(d, q, true) > r [[[["A", "INTEGER"], ["B", "INTEGER"]], [1, 1], [2, 2]]] > var q = [ ["select * from test", []], ["insert into test(a, b) values(?, ?)", [3, 3]] ] > var r = query(d, q, true) > r [[[["A", "INTEGER"], ["B", "INTEGER"]], [1, 1], [2, 2]], 1] - Singkong.jar comes with simple GUI database tool: - Table list (double-click to run SQL SELECT command) - Query tool (run SQL query) - Result: update count/result set (table) - Note: - Embedded Derby: only one Java Virtual Machine may open a database, another connection from different Java Virtual Machine to the same database will fail - In interactive evaluator > var d = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") > d DATABASE (URL=jdbc:derby:test;create=true, user=, driver=org.apache.derby.jdbc.EmbeddedDriver) > var d = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") > d DATABASE (URL=jdbc:derby:test;create=true, user=, driver=org.apache.derby.jdbc.EmbeddedDriver) - Please run another Singkong interpreter > var d = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") > d > type(d) "NULL" - Using db_util module (new in Singkong version 6.1): Functions in db_util module: create_field_from_array db_connect db_connect_embed db_connect_embed_ db_connect_embed_user db_create_table db_create_table_ db_create_table_derby db_create_table_derby_ db_create_table_embed db_create_table_embed_ db_create_table_postgresql db_create_table_postgresql_ db_delete db_delete_ db_driver db_insert db_insert_ db_last db_last_derby db_last_embed db_last_postgresql db_query_simple db_query_single db_run_query db_select db_select_ db_select_all db_select_all_ db_select_derby db_select_derby_ db_select_embed db_select_embed_ db_select_postgresql db_select_postgresql_ db_update db_update_ query_result load_module("db_util") A. Connect to database Functions: db_connect db_connect_embed db_connect_embed_ db_connect_embed_user Embedded Derby (database name: test) in current working directory (create, if not exists): > var d_embed = db_connect_embed("test") > d_embed DATABASE (URL=jdbc:derby:test;create=true, user=, driver=org.apache.derby.jdbc.EmbeddedDriver) Embedded Derby (database name: test) in home directory > var d_embed_user = db_connect_embed_user("test") > d_embed_user DATABASE (URL=jdbc:derby:/home/test/test;create=true, user=, driver=org.apache.derby.jdbc.EmbeddedDriver) Derby at localhost:1527, database name: test (create, if not exists), user: admin, password: admin > var d_derby = db_connect("derby", "//localhost:1527/test;create=true", "admin", "admin") > d_derby DATABASE (URL=jdbc:derby://localhost:1527/test;create=true, user=admin, driver=org.apache.derby.jdbc.ClientDriver) PostgreSQL at localhost, database name: test, user: test, password: test > var d_pgsql = db_connect("postgresql", "//localhost/test", "test", "test") > d_pgsql DATABASE (URL=jdbc:postgresql://localhost/test, user=test, driver=org.postgresql.Driver) B. Create table Functions: db_create_table db_create_table_derby db_create_table_embed db_create_table_postgresql Column types mapping: Derby or Embedded Derby: "id": "integer not null generated always as identity (start with 1, increment by 1)" "id.": "integer not null generated always as identity (start with 1, increment by 1) unique", "varchar": "varchar (32672)" "varchar.": "varchar (32672) default ''" "text": "clob" "text.": "clob default ''" "decimal": "decimal(19,4)" "decimal.": "decimal(19,4) default 0" "integer": "integer" "integer.": "integer default 0" "boolean": "boolean" "boolean.": "boolean default false" "date": "date" "date.": "date default current_date" "timestamp": "timestamp" "timestamp.": "timestamp default current_timestamp" PostgreSQL: "id": "serial" "id.": "serial unique", "varchar": "varchar" "varchar.": "varchar default ''" "text": "text" "text.": "text default ''" "decimal": "decimal(19,4)" "decimal.": "decimal(19,4) default 0" "integer": "integer" "integer.": "integer default 0" "boolean": "boolean" "boolean.": "boolean default false" "date": "date" "date.": "date default current_date" "timestamp": "timestamp" "timestamp.": "timestamp default current_timestamp" Example: Derby: > var r = db_create_table_derby(d_derby, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r [[0], "create table test_table (a integer not null generated always as identity (start with 1, increment by 1),b varchar (32672) default '',c clob default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)"] Embedded Derby: > var r = db_create_table_embed(d_embed, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r [[0], "create table test_table (a integer not null generated always as identity (start with 1, increment by 1),b varchar (32672) default '',c clob default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)"] PostgreSQL: > var r = db_create_table_postgresql(d_pgsql, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r [[0], "create table test_table (a serial,b varchar default '',c text default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)"] SQL command only (please notice the underscore in function name): > var r = db_create_table_derby_(d_derby, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r ["create table test_table (a integer not null generated always as identity (start with 1, increment by 1),b varchar (32672) default '',c clob default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)", []] > var r = db_create_table_embed_(d_embed, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r ["create table test_table (a integer not null generated always as identity (start with 1, increment by 1),b varchar (32672) default '',c clob default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)", []] > var r = db_create_table_postgresql_(d_pgsql, "test_table", [["a", "id"], ["b", "varchar."], ["c", "text."], ["d", "decimal."], ["e", "integer."], ["f", "boolean."], ["g", "date."], ["h", "timestamp."]]) > r ["create table test_table (a serial,b varchar default '',c text default '',d decimal(19,4) default 0,e integer default 0,f boolean default false,g date default current_date,h timestamp default current_timestamp)", []] C. Insert Derby: > var r = db_insert(d_derby, "test_table", {"b": "Test", "e": 12345}) > r [1] Embedded Derby: > var r = db_insert(d_embed, "test_table", {"b": "Test", "e": 12345}) > r [1] PostgreSQL: > var r = db_insert(d_pgsql, "test_table", {"b": "Test", "e": 12345}) > r [1] SQL command only (please notice the underscore in function name): > db_insert_(d_derby, "test_table", {"b": "Test", "e": 12345}) ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > db_insert_(d_embed, "test_table", {"b": "Test", "e": 12345}) ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > db_insert_(d_pgsql, "test_table", {"b": "Test", "e": 12345}) ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] D. Last insert id Derby: > db_last_derby(d_derby) 1 Embedded Derby: > db_last_embed(d_embed) 1 PostgreSQL: > db_last_postgresql(d_pgsql) 1 Derby: > var r = db_insert(d_derby, "test_table", {"b": "Test", "e": 12345}) > db_last_derby(d_derby) 2 Embedded Derby: > var r = db_insert(d_embed, "test_table", {"b": "Test", "e": 12345}) > db_last_embed(d_embed) 2 PostgreSQL: > var r = db_insert(d_pgsql, "test_table", {"b": "Test", "e": 12345}) > db_last_postgresql(d_pgsql) 2 E. Select all Derby: > db_select_all(d_derby, "test_table") [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:40], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:47:40]]] Embedded Derby: > db_select_all(d_embed, "test_table") [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:57], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:48:12]]] PostgreSQL: > db_select_all(d_pgsql, "test_table") [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:45:06], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:48:26]]] SQL command only > db_select_all_(d_derby, "test_table") ["select * from test_table ", []] > db_select_all_(d_embed, "test_table") ["select * from test_table ", []] > db_select_all_(d_pgsql, "test_table") ["select * from test_table ", []] F. Select (column, where, order, offset, fetch/limit) Derby: > db_select_derby(d_derby, "test_table", [], [], ["a asc", "b desc"], null, null) [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:40], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:47:40]]] > db_select_derby(d_derby, "test_table", ["a", "b"], [], ["a desc"], null, null) [[[2, "Test"], [1, "Test"]]] > db_select_derby(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) [[[2, "Test", ""], [1, "Test", ""]]] > db_select_derby(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) [[[1, "Test", ""]]] > db_select_derby(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) [[[2, "Test", ""]]] SQL command only > db_select_derby_(d_derby, "test_table", [], [], ["a asc", "b desc"], null, null) ["select * from test_table order by a asc,b desc", []] > db_select_derby_(d_derby, "test_table", ["a", "b"], [], ["a desc"], null, null) ["select a,b from test_table order by a desc", []] > db_select_derby_(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc", [0, 12345]] > db_select_derby_(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 1 rows ", [0, 12345]] > db_select_derby_(d_derby, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 0 rows fetch first 1 rows only ", [0, 12345]] Embedded Derby: > db_select_embed(d_embed, "test_table", [], [], ["a asc", "b desc"], null, null) [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:57], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:48:12]]] > db_select_embed(d_embed, "test_table", ["a", "b"], [], ["a desc"], null, null) [[[2, "Test"], [1, "Test"]]] > db_select_embed(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) [[[2, "Test", ""], [1, "Test", ""]]] > db_select_embed(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) [[[1, "Test", ""]]] > db_select_embed(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) [[[2, "Test", ""]]] SQL command only > db_select_embed_(d_embed, "test_table", [], [], ["a asc", "b desc"], null, null) ["select * from test_table order by a asc,b desc", []] > db_select_embed_(d_embed, "test_table", ["a", "b"], [], ["a desc"], null, null) ["select a,b from test_table order by a desc", []] > db_select_embed_(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc", [0, 12345]] > db_select_embed_(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 1 rows ", [0, 12345]] > db_select_embed_(d_embed, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 0 rows fetch first 1 rows only ", [0, 12345]] PostgreSQL: > db_select_postgresql(d_pgsql, "test_table", [], [], ["a asc", "b desc"], null, null) [[[1, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:45:06], [2, "Test", "", 0, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:48:26]]] > db_select_postgresql(d_pgsql, "test_table", ["a", "b"], [], ["a desc"], null, null) [[[2, "Test"], [1, "Test"]]] > db_select_postgresql(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) [[[2, "Test", ""], [1, "Test", ""]]] > db_select_postgresql(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) [[[1, "Test", ""]]] > db_select_postgresql(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) [[[2, "Test", ""]]] SQL command only > db_select_postgresql_(d_pgsql, "test_table", [], [], ["a asc", "b desc"], null, null) ["select * from test_table order by a asc,b desc", []] > db_select_postgresql_(d_pgsql, "test_table", ["a", "b"], [], ["a desc"], null, null) ["select a,b from test_table order by a desc", []] > db_select_postgresql_(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], null, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc", [0, 12345]] > db_select_postgresql_(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 1, null) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 1 rows ", [0, 12345]] > db_select_postgresql_(d_pgsql, "test_table", ["a", "b", "c"], [["a > ", 0, "and"], ["e = ", 12345, ""]], ["a desc"], 0, 1) ["select a,b,c from test_table where a > ? and e = ? order by a desc offset 0 rows limit 1", [0, 12345]] G. Update (all) Derby: > db_update(d_derby, "test_table", [], {"d": 12.34}) [2] Embedded Derby: > db_update(d_embed, "test_table", [], {"d": 12.34}) [2] PostgreSQL: > db_update(d_pgsql, "test_table", [], {"d": 12.34}) [2] SQL command only > db_update_(d_derby, "test_table", [], {"d": 12.34}) ["update test_table set d=?", [12.3400]] > db_update_(d_embed, "test_table", [], {"d": 12.34}) ["update test_table set d=?", [12.3400]] > db_update_(d_pgsql, "test_table", [], {"d": 12.34}) ["update test_table set d=?", [12.3400]] H. Update (where) Derby: > db_update(d_derby, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) [1] Embedded Derby: > db_update(d_embed, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) [1] PostgreSQL: > db_update(d_pgsql, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) [1] SQL command only > db_update_(d_derby, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) ["update test_table set d=? where e > ? and a < ? ", [12.3450, 10000, 2]] > db_update_(d_embed, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) ["update test_table set d=? where e > ? and a < ? ", [12.3450, 10000, 2]] > db_update_(d_pgsql, "test_table", [["e > ", 10000, "and"], ["a < ", 2, ""]], {"d": 12.345}) ["update test_table set d=? where e > ? and a < ? ", [12.3450, 10000, 2]] I. Delete (where) Derby: > db_delete(d_derby, "test_table", [["a > ", 1, ""]]) [1] Embedded Derby: > db_delete(d_embed, "test_table", [["a > ", 1, ""]]) [1] PostgreSQL: > db_delete(d_pgsql, "test_table", [["a > ", 1, ""]]) [1] SQL command only > db_delete_(d_derby, "test_table", [["a > ", 1, ""]]) ["delete from test_table where a > ? ", [1]] > db_delete_(d_embed, "test_table", [["a > ", 1, ""]]) ["delete from test_table where a > ? ", [1]] > db_delete_(d_pgsql, "test_table", [["a > ", 1, ""]]) ["delete from test_table where a > ? ", [1]] J. Simple query (without argument) Derby: > db_query_simple(d_derby, "select * from test_table") [[[1, "Test", "", 12.3450, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:40]]] Embedded Derby: > db_query_simple(d_embed, "select * from test_table") [[[1, "Test", "", 12.3450, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:44:57]]] PostgreSQL: > db_query_simple(d_pgsql, "select * from test_table") [[[1, "Test", "", 12.3450, 12345, false, 2021-08-15 00:00:00, 2021-08-15 17:45:06]]] K. Single query (with argument) Derby: > db_query_single(d_derby, "select a, b from test_table where a > ?", [0]) [[[1, "Test"]]] Embedded Derby: > db_query_single(d_embed, "select a, b from test_table where a > ?", [0]) [[[1, "Test"]]] PostgreSQL: > db_query_single(d_pgsql, "select a, b from test_table where a > ?", [0]) [[[1, "Test"]]] L. Delete (all) Derby: > db_delete(d_derby, "test_table", []) [1] > db_select_all(d_derby, "test_table") [[]] Embedded Derby: > db_delete(d_embed, "test_table", []) [1] > db_select_all(d_embed, "test_table") [[]] PostgreSQL: > db_delete(d_pgsql, "test_table", []) [1] > db_select_all(d_pgsql, "test_table") [[]] SQL command only > db_delete_(d_derby, "test_table", []) ["delete from test_table", []] > db_delete_(d_embed, "test_table", []) ["delete from test_table", []] > db_delete_(d_pgsql, "test_table", []) ["delete from test_table", []] M. Using built-in function: query Derby: Query (with transaction) OK > var q1 = db_insert_(d_derby, "test_table", {"b": "Test", "e": 12345}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > var q2 = db_update_(d_derby, "test_table", [], {"d": 12.34}) > q2 ["update test_table set d=?", [12.3400]] > query(d_derby, [q1, q2]) [1, 1] > db_select_all(d_derby, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:25:34]]] Query (with transaction) Failed (there is no column named 'error' in table test_table) > var q3 = db_update_(d_derby, "test_table", [], {"error": 12.34}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > q3 ["update test_table set error=?", [12.3400]] > println(query(d_derby, [q1, q3])) null > db_select_all(d_derby, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:25:34]]] Embedded Derby: Query (with transaction) OK > var q1 = db_insert_(d_embed, "test_table", {"b": "Test", "e": 12345}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > var q2 = db_update_(d_embed, "test_table", [], {"d": 12.34}) > q2 ["update test_table set d=?", [12.3400]] > query(d_embed, [q1, q2]) [1, 1] > db_select_all(d_embed, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:31:27]]] Query (with transaction) Failed (there is no column named 'error' in table test_table) > var q3 = db_update_(d_embed, "test_table", [], {"error": 12.34}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > q3 ["update test_table set error=?", [12.3400]] > println(query(d_embed, [q1, q3])) null > db_select_all(d_embed, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:31:27]]] PostgreSQL: Query (with transaction) OK > var q1 = db_insert_(d_pgsql, "test_table", {"b": "Test", "e": 12345}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > var q2 = db_update_(d_pgsql, "test_table", [], {"d": 12.34}) > q2 ["update test_table set d=?", [12.3400]] > query(d_pgsql, [q1, q2]) [1, 1] > db_select_all(d_pgsql, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:34:53]]] Query (with transaction) Failed (there is no column named 'error' in table test_table) > var q3 = db_update_(d_pgsql, "test_table", [], {"error": 12.34}) > q1 ["insert into test_table (b,e) values (?,?)", ["Test", 12345]] > q3 ["update test_table set error=?", [12.3400]] > println(query(d_pgsql, [q1, q3])) null > db_select_all(d_pgsql, "test_table") [[[3, "Test", "", 12.3400, 12345, false, 2021-08-15 00:00:00, 2021-08-15 18:34:53]]] ----------------------------------------------------------------------------- 10. Web application development Singkong supports simple web application development in form of Common Gateway Interface (CGI). - To output CGI headers, please use built-in function cgi_header. - To output text contents (for example, HTML), built-in function cgi_contents may be used. Manually printing to standard output using built-in function println (or print) is also supported. For example: cgi_header() print("Test") - To get QUERY_STRING (x-www-form-urlencoded) from HTTP GET request as HASH (decoded keys/values), please use built-in function cgi_get. - To get body (x-www-form-urlencoded) of HTTP POST request as HASH (decoded keys/values), please use built-in function cgi_post. - To get body (x-www-form-urlencoded) of HTTP POST (STRING of HASH) request as HASH (decoded keys (in strings)/values), please use built-in function cgi_post_hash. As an alternative, please use json module. - Both cgi_post and cgi_post_hash read up to HTTP server variable CONTENT_LENGTH, if set. Optional charset (supported by Java) may be passed, for example: US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE, UTF-16. - Using HTTP server with CGI support: - It is preferred to use .web filename extension (rather than .singkong) for Singkong CGI application (shorter to type, allows .singkong files to be downloaded). - Singkong CGI (.web files) may be placed inside document root or web application root, not in dedicated cgi-bin directory, and accessible as, for example: - http:///test.web - http:///example/test.web - Apache HTTP Server: - Required modules: - cgi or cgid (depending on used multi-processing module) - actions - Add handler for .web files as SingkongWeb, then activate a CGI script: (please add these lines to configuration file, for example, httpd.conf) AddHandler SingkongWeb .web Action SingkongWeb "/cgi-bin/singkongweb.cgi" - Example of singkongweb.cgi file: (please place this file in configured directory for serving cgi-bin, then set execute bit) (Singkong.jar is assumed to be installed in /opt) #!/bin/bash if [ -z "$PATH_TRANSLATED" ]; then printf "Status: 404 Not Found\n" printf "Content-type: text/plain\n\n" printf "not found\n" else java -DSINGKONG=0 -jar /opt/Singkong.jar "$PATH_TRANSLATED" fi - Internet Information Services (IIS): - Required application development features: - CGI - ISAPI Extensions - In Handler Mappings, Add Script Map...: - Request path: *.web - Executable: -DSINGKONG=0 -jar %s - Name: SingkongWeb (then allow this ISAPI extension) - Query string handling examples: a : {"a": ""} a&b : {"a": "", "b": ""} a=1 : {"a": "1"} a=1&a=2 : {"a": ["1", "2"]} a=1&a=2&a&b=1&b=2&b : {"a": ["1", "2", ""], "b": ["1", "2", ""]} a=1&a=2&A&b=1&b=2&b&c : {"a": ["1", "2"], "A": "", "b": ["1", "2", ""], "c": ""} a=hello+world : {"a": "hello world"} a=hello%20world! : {"a": "hello world!"} - Working with session: - Please use built-in module: web - Currently supported storage: file system (for example: "/tmp", "c:\session") - Session data (HASH) reserved keys (STRING): - session: session id - timestamp: timestamp (save) - Example: (please adjust sess_path variable, make sure it is writable by web server process) - index.web: if authenticated, redirect to home.web otherwise, redirect to login.web load_module("web") var sess_path = "c:\session" var sess = session_file_get(sess_path) if ((sess["user"] != null) & (sess["session"] != null)) { cgi_header( header_location("home.web") ) exit() } cgi_header( header_location("login.web") ) - login.web: if authenticated, redirect to home.web otherwise: - if request method is GET, show login form - if request method is POST, authenticate and set session load_module("web") var sess_path = "c:\session" var sess = session_file_get(sess_path) if ((sess["user"] != null) & (sess["session"] != null)) { cgi_header( header_location("home.web") ) exit() } if (request_method() == "GET") { var g = cgi_get() var error = "" if (g["error"] == "auth") { var error = "Authentication failed" } cgi_header() print(" Login " + error + "
Username: Password:
") exit() } if (request_method() == "POST") { var form = cgi_post() var u = form["u"] var p = form["p"] if (u == "admin" & p == "admin") { var sess = session_new() set(sess, "user", u) session_file_set(sess_path, sess) cgi_header( header_session(sess) + header_location("home.web") ) } else { cgi_header( header_location("login.web?error=auth") ) } exit() } - home.web: if not authenticated, redirect to login.web load_module("web") var sess_path = "c:\session" var sess = session_file_get(sess_path) if ((sess["user"] == null) | (sess["session"] == null)) { cgi_header( header_location("login.web") ) exit() } cgi_header() print(" Home Hello, " + sess["user"] + " logout
") - logout.web: delete session and redirect to index.web load_module("web") var sess_path = "c:\session" var sess = session_file_get(sess_path) session_file_delete(sess_path, sess) cgi_header( header_session_delete(sess) + header_location("login.web") ) ----------------------------------------------------------------------------- 11. Simple HTTP client Singkong comes with simple HTTP client. - Default user agent: Singkong - Encode or decode: please use built-in functions url_encode or url_decode - Base64: please use built-in functions base64_decode, base64_encode, x_base64_decode_file, or x_base64_encode_file - Read timeout can be set for each request (default: 60 seconds) - Headers may be set for each request - HEAD - built-in function: http_head - Accept-Charset: UTF-8 - Redirection from HTTP to HTTPS is supported - Only one redirect is followed (Location header) - Supported redirects: 301, 302, 303 - Built-in function returns ARRAY (headers, response code, data) or NULL - GET - built-in functions: http_get, http_get_file - Accept-Charset: UTF-8 - Redirection from HTTP to HTTPS is supported - Only one redirect is followed (Location header) - Supported redirects: 301, 302, 303 - To download a file, please use built-in function: http_get_file - Built-in functions return ARRAY (headers, response code, data) or NULL - POST - built-in functions: http_post, http_post_override - Accept-Charset: UTF-8 - Content-Type: application/x-www-form-urlencoded;charset=UTF-8 - Body may be empty - To do a HTTP POST with X-HTTP-Method-Override, please use built-in function http_post_override - Built-in functions return ARRAY (headers, response code, data) or NULL - PUT - built-in function: http_put - Accept-Charset: UTF-8 - Content-Type: application/x-www-form-urlencoded;charset=UTF-8 - Body may be empty - Built-in function returns ARRAY (headers, response code, data) or NULL - DELETE - built-in function: http_delete - Accept-Charset: UTF-8 - Content-Type: application/x-www-form-urlencoded;charset=UTF-8 - Body is not supported - Built-in function returns ARRAY (headers, response code, data) or NULL - Unsupported method - If the server supports X-HTTP-Method-Override, http_post_override probably can be used (for example, with PATCH request method) - Working with JSON - Please use json built-in module. > load_module("json") - For JSON parsing, please use function json_parse from json module (returns empty STRING on error): > type(json_parse("null")) "NULL" > json_parse("true") true > json_parse("false") false > json_parse("1.23") 1.2300 > json_parse("[null, true, 1.23, {}]") [null, true, 1.2300, {}] > json_parse("{1: 2, true: []}") "" > json_parse(string({1: 2, true: []})) "" > json_parse(json_string({1: 2, true: []})) {"1": 2, "true": []} > json_parse("test") "" > json_parse(json_string("test")) "test" - To convert to JSON STRING, please use function json_string from json module. > json_string({1: [null, true, {}], 2: 3}) "{"1": [null, true, {}], "2": 3}" - Additional built-in functions: - http_response_ok: on successful HTTP request (returns ARRAY (headers, response code, data)), if the response status code is 200, returns the data. Otherwise, returns NULL. ----------------------------------------------------------------------------- 12. Working with threads Singkong comes with simple multithreading support. - To create and start a new thread, optionally with intrinsic lock, please use built-in function thread. - To test if a thread is alive, please use built-in function thread_alive. - To wait for a thread to finish, please use built-in function thread_join. - Example of single-threaded program: (output: [100000]) var a = [0] var f = fn() { var i = 0 repeat { set(a, 0, a[0] + 1) var i = i + 1 if (i >= 100000) { return i } } } f() println(a) - Multithreaded without intrinsic lock: (output may differ for each run) var a = [0] var f = fn() { var i = 0 repeat { set(a, 0, a[0] + 1) var i = i + 1 if (i >= 100000) { return i } } } var t1 = thread(f) var t2 = thread(f) thread_join(t1) thread_join(t2) println(a) - Multithreaded with intrinsic lock: (output: [200000]) var a = [0] var f = fn() { var i = 0 repeat { set(a, 0, a[0] + 1) var i = i + 1 if (i >= 100000) { return i } } } var t1 = thread(f, a) var t2 = thread(f, a) thread_join(t1) thread_join(t2) println(a) - Multithreaded with intrinsic lock (using null, true, or false as lock): - null, true, or false (output: [200000]): var a = [0] var f = fn() { var i = 0 repeat { set(a, 0, a[0] + 1) var i = i + 1 if (i >= 100000) { return i } } } var t1 = thread(f, null) var t2 = thread(f, null) thread_join(t1) thread_join(t2) println(a) - warning: threads below are not using the same lock: var a = [0] var f = fn() { var i = 0 repeat { set(a, 0, a[0] + 1) var i = i + 1 if (i >= 100000) { return i } } } var t1 = thread(f, "Singkong") var t2 = thread(f, "Singkong") thread_join(t1) thread_join(t2) println(a) - Simulating long running process in GUI var f = fn() { delay(random(5000, 10000)) } var t = thread(f) var s = number(@) var c = fn() { if (thread_alive(t)) { var m = number(@) - s statusbar(0, m + " ms", true) statusbar(1, "busy", true) } else { statusbar(1, "idle", true) stop() } } reset() timer(random(100, 500), c) show() ----------------------------------------------------------------------------- 13. Calling Java method It is possible to call static Java method and use the returned value, to provide functionalities in Java Programming Language. - To call static Java method, please use built-in function call. This function accepts two-arguments: STRING (Java class name found in class path) and any type. This function returns STRING, ARRAY (of STRING), ARRAY (of ARRAY (of STRING)), or NULL (error). - Please note that Java class name must be found in class path. In order to use class from another jar file/directory, please run Singkong using the following command: java -cp Singkong.jar: com.noprianto.singkong.Singkong or java -cp Singkong.jar: com.noprianto.singkong.Singkong or java -cp Singkong.jar; com.noprianto.singkong.Singkong or java -cp Singkong.jar; com.noprianto.singkong.Singkong where is a jar file and is a directory containing the class. Please note that class path separator character is operating system dependent (: or ;). If the class file is located in current working directory, might be replaced with a ., for example: java -cp Singkong.jar:. com.noprianto.singkong.Singkong (please also read about deployment and bundling with interpreter file) - Called method must be static method named: singkong. It must require a String[][] and return a String, a String[], or a String[][]. - When the method is called, Singkong interpreter will use STRING representation of Singkong value passed to Java method, if necessary. Singkong will never pass a Java null to the method. - If it is not an ARRAY: - String[1][] will be created - Its first element will be a String[1], containing a single element of STRING representation of Singkong value passed to this method - If it is an ARRAY: - String[][] will be created, whose length is the length of passed ARRAY - For each element in this ARRAY: - If it is not an ARRAY: - String[1] will be created, containing STRING representation of the element - If it is an ARRAY: - String[] will be created, whose length is the length of this ARRAY - This newly created String[] will be populated with STRING representation of each element in this ARRAY - If there is any error when calling this method, NULL will be returned. If the method returns something other than String, String[], or String[][], NULL will be returned. If an exception occurred, NULL will be returned. - Example 1: - HelloWorld.java in examples directory. Please make sure there is a static method: public static String singkong(String[][] args) { public class HelloWorld { public static String singkong_info() { return "HelloWorld: Description, license, ..."; } public static String singkong(String[][] args) { StringBuilder builder = new StringBuilder(); for (int i=0; i var x = call("HelloWorld", [[], [1], [2,3], [4,5,6]]) > x "; 1, ; 2, 3, ; 4, 5, 6, ; " > type(x) "STRING" - Example 2: - HelloWorldArray.java in examples directory. Please make sure there is a static method: public static String[] singkong(String[][] args). Please note that this method returns String[], not String. public class HelloWorldArray { public static String singkong_info() { return "HelloWorldArray: Description, license, ..."; } public static String[] singkong(String[][] args) { String[] ret = new String[args.length]; for (int i=0; i var x = call("HelloWorldArray", [[], [1], [2,3], [4,5,6]]) > x ["", "1, ", "2, 3, ", "4, 5, 6, "] > type(x) "ARRAY" > each(x, fn(x, y){ println(y + ": " + type(x) + ": " + x)}) 0: STRING: 1: STRING: 1, 2: STRING: 2, 3, 3: STRING: 4, 5, 6, - Example 3: - HelloWorldArrayArray.java in examples directory. Please make sure there is a static method: public static String[][] singkong(String[][] args). Please note that this method returns String[][], not String[]. public class HelloWorldArrayArray { public static String singkong_info() { return "HelloWorldArrayArray: Description, license, ..."; } public static String[][] singkong(String[][] args) { String[][] ret = new String[args.length][]; for (int i=0; i var x = call("HelloWorldArrayArray", [[], [1], [2,3], [4,5,6]]) > x [[], ["1"], ["2", "3"], ["4", "5", "6"]] > each(x, fn(x, y){ println(y + ": " + type(x) + ": " + x)}) 0: ARRAY: [] 1: ARRAY: ["1"] 2: ARRAY: ["2", "3"] 3: ARRAY: ["4", "5", "6"] - Returned String from Java method can be a valid Singkong program, that can be evaluated using built-in function eval (if this function is not disabled): - HelloWorldEval.java: public class HelloWorldEval { public static String singkong_info() { return "HelloWorldEval: Description, license, ..."; } public static String singkong(String[][] args) { StringBuilder builder = new StringBuilder(); for (int i=0; i var code = call("HelloWorldEval", [[], [1], [2,3], [4,5,6]]) > code "component("button", "; 1, ; 2, 3, ; 4, 5, 6, ; ")" > var b = eval(code) > b COMPONENT: button (; 1, ; 2, 3, ; 4, 5, 6, ; ) > add(b) > show() - To get information about the class (if provided), please use built-in function call_info. This function accepts one argument: STRING (Java class name found in class path). The class must have a static method named: singkong_info, without parameter, and return a String. > call_info("HelloWorld") "HelloWorld: Description, license, ..." > call_info("HelloWorldArray") "HelloWorldArray: Description, license, ..." > call_info("HelloWorldArrayArray") "HelloWorldArrayArray: Description, license, ..." > call_info("HelloWorldEval") "HelloWorldEval: Description, license, ..." ----------------------------------------------------------------------------- 14. Embedding Singkong into another applications Java applications: - Note: after evaluating Singkong code, calling System.exit() explicitly might be needed, to terminate the application. - Class: com.noprianto.singkong.Singkong - Methods: public static java.lang.String evaluatorString(java.lang.String); public static java.lang.String evaluatorString(java.lang.String, java.lang.String[]); public static java.lang.String evaluatorString(java.lang.String, com.noprianto.singkong.SingkongEnvironment); public static java.lang.String evaluatorString(java.lang.String, com.noprianto.singkong.SingkongEnvironment,java.lang.String[]); public static void evaluatorString(java.lang.String, com.noprianto.singkong.SingkongEnvironment,java.io.PrintStream); public static void evaluatorString(java.lang.String, com.noprianto.singkong.SingkongEnvironment,java.io.PrintStream, java.lang.String[]); - From java.util.Map to SingkongEnvironment: public static com.noprianto.singkong.SingkongEnvironment environmentFromMap( java.util.Map); Example 1: Test.java import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { String code = "var list = [1,2,3] println(list)"; String output = Singkong.evaluatorString(code); System.out.println(output); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test [1, 2, 3] Example 2: Test.java import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { String code = "println([1,2,3]) system()"; String output = Singkong.evaluatorString(code, new String[]{"system"}); System.out.println(output); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test [1, 2, 3] ERROR: [line: 1] built-in function "system" is disabled Example 3: Test.java import java.util.Map; import java.util.HashMap; import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { Map map = new HashMap(); map.put("hello", "Hello, World"); map.put("test", true); String code = "println(hello) println(test)"; String output = Singkong.evaluatorString(code, Singkong.environmentFromMap(map)); System.out.println(output); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test Hello, World true Example 4: Test.java import java.util.Map; import java.util.HashMap; import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { Map map = new HashMap(); map.put("hello", "Hello, World"); String code = "println(hello) info()"; String output = Singkong.evaluatorString(code, Singkong.environmentFromMap(map), new String[]{"system", "info"}); System.out.println(output); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test Hello, World ERROR: [line: 1] built-in function "info" is disabled Example 5: Test.java import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { String result = ""; Map map = new HashMap(); map.put("hello", "Hello, World"); map.put("test", true); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { PrintStream output = new PrintStream(outputStream); Singkong.evaluatorString("println(hello) println(test)", Singkong.environmentFromMap(map), output); result = outputStream.toString(); } catch (Exception e) { } System.out.println(result); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test Hello, World true Example 6: Test.java import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import com.noprianto.singkong.Singkong; public class Test { public static void main(String[] args) { String result = ""; Map map = new HashMap(); map.put("hello", "Hello, World"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { PrintStream output = new PrintStream(outputStream); Singkong.evaluatorString("println(hello) info()", Singkong.environmentFromMap(map), output, new String[]{"system", "info"}); result = outputStream.toString(); } catch (Exception e) { } System.out.println(result); } } javac -cp Singkong.jar Test.java java -cp Singkong.jar:. Test Hello, World ERROR: [line: 1] built-in function "info" is disabled Another applications (not embedding): Example 7: Python >>> import subprocess >>> c = ['java', '-jar', 'Singkong.jar', 'println("singkong")'] >>> o = subprocess.check_output(c) >>> print(o) singkong Example 8: Python >>> import subprocess >>> c = ['java', '-DDISABLE=info', '-jar', 'Singkong.jar', 'info()'] >>> o = subprocess.check_output(c) >>> print(o) ERROR: [line: 1] built-in function "info" is disabled ----------------------------------------------------------------------------- 15. Deployment To run applications developed using Singkong programming language, target system must have Java Runtime Environment installed (Java 5.0 or later). It is technically possible to bundle Java Runtime Environment with Singkong interpreter and applications developed using Singkong, but this is beyond the scope of Singkong documentation. Assummed that Java Runtime Environment is installed (Java 5.0 or later), before Singkong version 3.5, user should download Singkong.jar and application file (*.singkong) then run the application using the following options: (1) Run Singkong.jar, open file, run (GUI available) - java -jar Singkong.jar (double-clicking Singkong.jar may also work) - switch to Editor tab, Open the file (Open button) - click Run button Note: this option requires multiple steps and requires user to know the location of application file (using file open dialog). It is not practical and too technical. Also, if double-clicking Singkong.jar is not working, this option requires user to access command line or terminal emulator, which adds another steps. (2) Run Singkong.jar with arguments - java -jar Singkong.jar Note: this option requires user to access command line or terminal emulator, and requires user to know the location of application file. It is probably too technical. Batch file or shell script may ease this option, but still, user may need to download multiple files. (3) Embedding Singkong interpreter Note: this requires writing code, for example, in Java Since Singkong version 3.5, the following option is available: (4) Bundle application file with Singkong interpreter This results in only one runnable jar file to distribute. To bundle application file with Singkong interpreter: - The application file must be named: main.singkong - The file must be added to Singkong.jar using jar/zip compatible program, for example: jar jar uf Singkong.jar main.singkong - Singkong.jar must be renamed to another name (which does not contain "Singkong"). - Optional: another packages (Java class) may be added along with application file - Optional: to add resource files: - Put all resource files in /resource directory, in interpreter jar file for example: /resource/file.png /resource/test.txt - Please also read about GUI application development - To copy a resource to a file, please use built-in function copy_resource - To evaluate/run Singkong code saved as resource, please use built-in function load_resource - To evaluate/run Singkong code (file in current working directory or (if failed), /resource/ in interpreter file), please use built-in function load_file_or_resource ----------------------------------------------------------------------------- 16. Differences with Monkey Programming Language - Singkong is case-insensitive - Assignment is done using var statement (let is supported, for compatibility) - Identifier starts with a letter or an underscore and optionally followed by letters, numbers, or underscores - null literal - null is not printed - Singkong has no INTEGER data type. Singkong has NUMBER data type that is compatible with INTEGER in Monkey, with support for decimal numbers. - Additional operators for NUMBER: % ^ <= >= - Additional operators for BOOLEAN: & | - HASH: any data type can be used as key/value, maintains insertion-order - More built-in functions and modules - Additional data types: DATE, COMPONENT (GUI application development), and DATABASE (database connection, database application development) - Singkong uses operators for operations on data types, wherever possible (for example: * for string repeat, - for string remove, - for array remove, and many more). - Repeat loop (and built-in functions: do, each for simple looping) - Some (or all) built-in functions may be disabled when the interpreter in run - Singkong supports multi-line comment (between # and ;) - Singkong supports documentation string for FUNCTION - There is a Monkey implementation in Singkong (monkey.singkong): monkey.singkong: https://nopri.github.io/monkey.singkong monkey.singkong runnable jar: https://nopri.github.io/monkeyinterpreter.jar (bundled with Singkong interpreter) ----------------------------------------------------------------------------- 17. Example: GUI: components reset() var b = component("button", "Hello") var c = component("checkbox", "Singkong?") var m = component("combobox", "Singkong,Programming,Language") var d = component("date", "EEEE, yyyy-MMMM-dd") var e = component("edit", "Hello, World") var i = component("image", "image.jpg") var l = component("label", "Singkong Programming Language") var p = component("password", "test") var sp = component("spin", "1,0,10,2") var g = component("progress", "") config(g, "contents", 50) var r = component("radio", "Radio Button") var a = component("tab", "") var panel = component("panel", "Panel") var t1 = component("table", "A,B,C,D,E") var grid = component("grid", "Grid") var t2 = component("table", "A,B,C,D,E") var x = component("text", "Singkong") var v = component("view", "Singkong
Programming") var s = component("mask", "(###) ###-###") var dr = component("draw", "50, 50") config(dr, "foreground", "black") config(dr, "background", "white") draw_string(dr, ":)", 20, 22) panel_add(panel, t1, 10, 10, 250, 400) tab_add(a, panel) grid_add(grid, t2, 0, 0, 1, 1, 1, 1, 3, 0, 5, 5, 5, 5) tab_add(a, grid) var bc = component("barchart", "") config(bc, "foreground", "black") config(bc, "background", "white") config(bc, "font", ["monospaced", 1, 20]) config(bc, "text", "Bar Chart") config(bc, "contents", [[10, "A (10)", "red"], [20, "B (20)", "green"], [30, "C (30)", "blue"]]) var pc = component("piechart", "") config(pc, "foreground", "black") config(pc, "background", "white") config(pc, "font", ["monospaced", 1, 20]) config(pc, "text", "Pie Chart") config(pc, "contents", [[40, "D (40)", "red"], [50, "E (50)", "green"], [60, "F (60)", "blue"]]) var grid_chart = component("grid", "Grid") grid_add(grid_chart, bc, 0, 0, 1, 1, 1, 1, 3, 0) grid_add(grid_chart, pc, 0, 1, 1, 1, 1, 1, 3, 0) var ge = component("grid", "") load_module("ui_calendar") var dd = part(@) var ca = create_calendar_simple_compact(dd[0], dd[1]) grid_add(ge, e, 0, 0, 1, 1, 1, 1, 3, 0) grid_add(ge, ca, 0, 1, 1, 1, 1, 1, 3, 0) add([ge, a, grid_chart]) add_n([i, l, x, p, c, r, m, b]) add_s([v, d, sp, g, s, dr]) each(range(0,8), fn(e, i) { statusbar(e, "Status: " + e, i%2 == 0) }) menubar([ ["File", 0, [ ["Quit", 0, true, fn() {frame_close()}] ]], ["Help", 0, [ ["About", 0, true, fn() {message("Singkong")}] ]] ]) closing("Are you sure you want to quit this application?", "Please confirm") show() ----------------------------------------------------------------------------- 18. Example: GUI: layout reset() var c = component("panel", "") var g = component("grid", "") var e = component("button", "E") var n = component("button", "N") var w = component("button", "W") var s1 = component("button", "S 1") var s2 = component("button", "S 2") var s3 = component("button", "S 3") var s = [s1, s2, s3] add([c, g]) add_e(e) add_n(n) add_s(s) add_w(w) # panel (absolute positioning); var items = range(0, 4) each(items, fn(i, counter) { var y = (20 + 80) * counter panel_add(c, component("button", string(i+1)), 50, y, 80, 80) }) # grid (grid layout); var b1 = component("button", "Button 1") var b2 = component("button", "Button 2") var b3 = component("button", "Button 3") var b4 = component("button", "Button 4") var b5 = component("button", "Button 5") grid_add(g, b1, 0, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) grid_add(g, b2, 1, 0, 1, 1, 0.5, 1.0, 3, 0, 5, 5, 5, 5) grid_add(g, b3, 0, 1, 2, 1, 1.0, 0.5, 3, 0, 5, 5, 5, 5) grid_add(g, b4, 0, 2, 1, 1, 0.5, 0.5, 0, 1, 5, 5, 5, 5) grid_add(g, b5, 1, 2, 1, 1, 0.5, 0.5, 0, 2, 5, 5, 5, 5) show() ----------------------------------------------------------------------------- 19. Example: GUI: Singkong information reset() var t = component("table","KEY,VALUE,TYPE", true) var l = component("label", "Singkong Programming Language information") add_n(l) add(t) var s = singkong() var a = [] var f = fn(x,i) { var v = s[x] var a = a + [x, v, type(v)] } each(keys(s), f) config(t, "contents", a) show() ----------------------------------------------------------------------------- 20. Example: GUI: event handlers reset() var b = component("button", "Hello World (mouse event)") var c = component("checkbox", "Singkong?") var r = component("radio", "Radio Button") var m = component("combobox", "Singkong,Programming,Language") var t = component("table", "A,B", true) var tm = component("table", "TYPE, X, Y, CLICK COUNT", true) var tk = component("table", "TYPE, CHAR, CODE, KEY, ACTION, MODIFIER, LOCATION, ALT, ALT GRAPH, CONTROL, META, SHIFT", true) var tf = component("table", "TYPE, WIDTH, HEIGHT, X, Y", true) var pm = component("panel", "Mouse") var pk = component("panel", "Keyboard") var pf = component("panel", "Frame") var ta = component("tab", "") var e = component("edit", "") var p = component("password", "") var x = component("text", "") var v = component("view", "") panel_add(pm, tm, 10, 10, 300, 500) tab_add(ta, pm) panel_add(pk, tk, 10, 10, 300, 500) tab_add(ta, pk) panel_add(pf, tf, 10, 10, 300, 500) tab_add(ta, pf) var bb = fn() { message(get(b,"text")) } event(b, bb) var cc = fn() { message(get(c,"active")) } event(c, cc) var rr = fn() { message(get(r,"active")) } event(r, rr) var mm = fn() { message(get(m, "text")) } event(m, mm) config(t, "contents", [[1,2],[3,4],[5,6]]) var tt = fn() { message(get(t, "contents")[get(t, "active")]) } event(t, tt) var ee = fn() { var content = get(e, "contents") config(v, "contents", content) } event(e, ee) var pp = fn() { message(get(p, "contents")) } event(p, pp) var xx = fn() { message(get(x, "contents")) } event(x, xx) event_mouse(b, fn(m) { table_add(tm, [m]) if (m[0] == "EXITED") { var c = len(get(tm, "contents")) table_scroll(tm, c-1) } }) event_keyboard_frame(fn(m) { table_add(tk, [m]) }) event_frame(fn(m) { table_add(tf, [m]) }) event(ta, fn() { message(get(ta, "active")) }) add_n([p, x]) add_s([b, c, r, m]) add([t, e, v, ta]) closing("Are you sure you want to quit this application?", "Please confirm") show() ----------------------------------------------------------------------------- 21. Example: GUI: printing reset() var header = component("text", "header") var footer = component("text", "footer") var b_print = component("button", "print") var b_table_print = component("button", "print table") var b_edit_print = component("button", "print edit") var table = component("table", "Key, Value") table_add(table, [ ["Singkong", "Programming Language"] ]) var edit = component("edit", "Singkong Programming Language") add_n([header, footer]) add([table, edit]) add_s([b_print, b_table_print, b_edit_print]) event(b_print, fn() { var t = ["Singkong", "Programming", "Language"] var s = 16 var x = 150 var y = 150 var font = "monospaced" printer(t, s, x, y, font) }) event(b_table_print, fn() { table_print(table, get(header, "contents"), get(footer, "contents")) }) event(b_edit_print, fn() { x_edit_print(edit, get(header, "contents"), get(footer, "contents")) }) show() ----------------------------------------------------------------------------- 22. Example: GUI: simple text file editor reset() var e = component("edit", "") var o = component("button", "open") var s = component("button", "save") var l = component("label", "") var oo = fn() { var f = open() if (!empty(f)) { config(e, "contents", read(f)) config(l, "text", f) } } event(o, oo) var ss = fn() { var f = save() if (!empty(f)) { var t = get(e, "contents") write(f, t) config(l, "text", f) } } event(s, ss) add_n(l) add(e) add_s([o, s]) show() ----------------------------------------------------------------------------- 23. Example: GUI: timer reset() var l1 = component("label", string(@)) var l2 = component("label", string(@)) var b1 = component("button", "toggle timer 1") var b2 = component("button", "toggle timer 2") var b3 = component("button", "stop all timers") var b4 = component("button", "start all timers") add([l1, l2]) add_s([b1, b2, b3, b4]) var f1 = fn() { config(l1, "text", string(@)) } var f2 = fn() { config(l2, "text", string(@)) } var t1 = timer(1000, f1) var t2 = timer(1000, f2) event(b1, fn() { if (timer_running(t1)) { stop_timer(t1) } else { start_timer(t1) } }) event(b2, fn() { if (timer_running(t2)) { stop_timer(t2) } else { start_timer(t2) } }) event(b3, fn() { stop() }) event(b4, fn() { start() }) show() ----------------------------------------------------------------------------- 24. Example: Database: create table, insert, update, select, GUI Using db_util module: load_module("db_util") reset() var t = component("table", "A,B", true) add(t) var d = db_connect_embed("test") if (d != null) { db_create_table_embed(d, "test", [["a", "integer."], ["b", "varchar."]]) db_insert(d, "test", {"a": random(0,100), "b": "hello"}) db_update(d, "test", [["b = ", "hello", ""]], {"b": "Hello World"}) var r = db_select_all(d, "test") if (!empty(r)) { config(t, "contents", r[0]) } } show() Using SQL command directly: reset() var t = component("table", "A,B", true) add(t) var d = database("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:test;create=true", "", "") if (d != null) { var q = [ ["create table test(a integer, b varchar(64))", []] ] var r = query(d, q) var q = [ ["insert into test(a,b) values(?, ?)", [random(0,100), "hello"]], ["update test set b=? where b=?", ["Hello World", "hello"]] ] var r = query(d, q) var q = [ ["select a,b from test", []] ] var r = query(d, q) if (!empty(r)) { config(t, "contents", r[0]) } } show() ----------------------------------------------------------------------------- 25. Example: Web: variables, GET, POST, JSON Example 1: environment variables index.web var e = env() var c = [ " Singkong Remote address: " + e["REMOTE_ADDR"] + "
HTTP user agent: " + e["HTTP_USER_AGENT"] + " " ] cgi_header() cgi_contents(c) Example 2: GET request (query string: min=&max=; response: JSON) random.web load_module("json") var h = {"Content-Type": "application/json"} var r = {"random": null} var p = cgi_get() var x = p["min"] var y = p["max"] if (is(x, "STRING") & is(y, "STRING")) { var z = random(number(x), number(y)) if (z != null) { set(r, "random", z) } } cgi_header(h) println(json_string(r)) Example 3: POST request (body: username=&password=; response: JSON) auth.web load_module("json") var h = {"Content-Type": "application/json"} var r = {"result": false} var p = cgi_post() var x = p["username"] var y = p["password"] if (is(x, "STRING") & is(y, "STRING")) { set(r, "result", x == "admin" & y == "admin") } cgi_header(h) println(json_string(r)) HTTP client: login.singkong load_module("json") var url = trim(input("URL", "Login")) if (!startswith(url, "http")) { exit() } var login = login_dialog("Login") if (len(login) == 2) { var u = login[0] var p = login[1] var data = "username=" + u + "&password=" + p var res = http_post(url, data) if (res != null) { if (is(res, "ARRAY")) { if (len(res) == 3) { var r = json_parse(res[2]) message(r["result"]) } } } } ----------------------------------------------------------------------------- 26. Example: Java: custom dialog For simpler (but easier) custom dialog using only Singkong code, please use built-in function panel_dialog (modal) or custom_dialog (modal, modeless). Please read: Calling Java method. The following file is saved in examples directory, relative to current working directory. - Dialog.java (compiled to Dialog.class: javac Dialog.java) import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JTextField; public class Dialog extends JDialog implements ActionListener{ private String value; private String param; private JTextField text; private JButton button; public Dialog(String param) { setModal(true); value = ""; this.param = param; text = new JTextField(); button = new JButton(param); button.addActionListener(this); add(text); add(button, BorderLayout.SOUTH); pack(); } public String getValue() { setVisible(true); return value; } public void actionPerformed(ActionEvent e) { value = text.getText(); setVisible(false); dispose(); } public static String singkong_info() { return "Dialog: Custom dialog in Java"; } public static String singkong(String[][] args) { String param = ""; if (args.length > 0) { String[] r = args[0]; if (r.length > 0) { param = r[0]; } } Dialog dialog = new Dialog(param); return dialog.getValue(); } public static void main(String[] args) { System.out.println("Dialog: Singkong Programming Language module"); } } - Run Singkong: java -cp Singkong.jar:examples com.noprianto.singkong.Singkong or (depending on the operating system) java -cp Singkong.jar;examples com.noprianto.singkong.Singkong - Singkong program can call the method: > var value = call("Dialog", "Singkong") (enter Hello World in the text field of dialog) (click the Singkong button) > value "Hello World" ----------------------------------------------------------------------------- 27. Example: Monkey programming language interpreter in Singkong Download monkey.singkong: https://nopri.github.io/monkey.singkong Download monkey.singkong runnable jar: https://nopri.github.io/monkeyinterpreter.jar (bundled with Singkong interpreter) ----------------------------------------------------------------------------- 28. Example: Working with Comma-Separated Values file (since Singkong version 4.6) - test.csv in current working directory id,name 1,"Hello,World" 2,Singkong - Load csv module: (csv is a built-in module; written in Singkong) > load_module("csv") - From CSV STRING to ARRAY: > var a = csv_from_string(",", read("test.csv")) > a [["id", "name"], ["1", "Hello,World"], ["2", "Singkong"], []] - From ARRAY to CSV STRING: (for example, using ; as separator) > write("output.csv", csv_to_string(";", a)) true - From CSV STRING to ARRAY, using ; as separator: > var b = csv_from_string(";", read("output.csv")) > b [["id", "name"], ["1", "Hello,World"], ["2", "Singkong"]] - Applying functions: > var c = csv_functions(slice(b, 1, len(b)), [number]) > c [[1, "Hello,World"], [2, "Singkong"]] - Double quotes will be automatically added: ("Hello, World" contains ,; default separator: ,) > b [["id", "name"], ["1", "Hello,World"], ["2", "Singkong"]] > write("output-test.csv", csv_to_string_default(b)) true > print(read("output-test.csv")) id,name 1,"Hello,World" 2,Singkong - Embedded newline is supported: > write("out.csv", csv_to_string_default([[1, "singkong" + newline() + "programming language"]])) true > print(read("out.csv")) 1,"singkong programming language" > var d = csv_from_string_default(read("out.csv")) > d [["1", "singkong programming language"]] > rect_array_size(d) [1, 2] - Note: embedded double quote is not supported ----------------------------------------------------------------------------- 29. Example: Drawing: simple painting program reset() var name = "Paint" var width = 1024 var height = 768 var font_size = 20 size(width, height) resizable(false) title(name) closing("Are you sure you want to quit this application?", "Please confirm") var g = component("grid", "") var d = component("draw", "" + width + "," + height) config(d, "background", "white") var gn = component("grid", "") var bf = component("button", "Foreground") var bb = component("button", "Background") var pf = component("panel", "") var pb = component("panel", "") var lx = component("label", "Pixel (+/-)") var xr = component("text", "0") var xg = component("text", "0") var xb = component("text", "0") var bx = component("button", "Set") var br = component("button", "Open") var bw = component("button", "Save (.png)") var pp = component("panel", "") var po = component("label", " ") grid_add(gn, bf, 0, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gn, pf, 1, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, bb, 2, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gn, pb, 3, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, lx, 4, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, xr, 5, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, xg, 6, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, xb, 7, 0, 1, 1, 0.1, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, bx, 8, 0, 1, 1, 0, 0, 3, 1, 4, 4, 4, 4) grid_add(gn, br, 9, 0, 1, 1, 0.2, 1, 3, 1, 4, 4, 4, 4) grid_add(gn, bw, 10, 0, 1, 1, 0.2, 0, 1, 1, 4, 4, 4, 4) grid_add(gn, pp, 11, 0, 1, 1, 1, 0, 3, 1, 4, 4, 4, 4) var gd = component("grid", "") config(gd, "border", "Draw") var rl = component("radio", "Line") var rt = component("radio", "Text") config(rl, "active", true) var rr = component("radio", "Rectangle") var ro = component("radio", "Oval") radio_group([rl, rt, rr, ro]) config(pf, "background", "black") config(pb, "background", "white") var cf = component("checkbox", "Fill") config(cf, "active", true) var cl = component("label", "Width") var cw = component("combobox", "1,2,3,4,5,6,7,8") var gt = component("grid", "") config(gt, "border", "Text") var co = component("combobox", join(",", fonts())) var ts = component("text", "" + font_size) var tt = component("text", "") grid_add(gd, rl, 0, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, rt, 1, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, rr, 2, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, ro, 3, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, cf, 4, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, cl, 5, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gd, cw, 6, 0, 1, 1, 1, 0, 0, 1, 4, 4, 4, 4) grid_add(gt, co, 0, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gt, ts, 1, 0, 1, 1, 0, 0, 0, 1, 4, 4, 4, 4) grid_add(gt, tt, 2, 0, 1, 1, 1, 0, 3, 1, 4, 4, 4, 4) grid_add(g, gn, 0, 0, 2, 1, 0, 0, 1, 1, 4, 4, 4, 4) grid_add(g, d, 0, 1, 2, 1, 1, 1, 3, 0, 4, 4, 4, 4) grid_add(g, gd, 0, 2, 1, 1, 1, 0, 3, 1, 4, 4, 4, 4) grid_add(g, gt, 1, 2, 1, 1, 1, 0, 3, 1, 4, 4, 4, 4) add(g) add_s(po) var x = [null, null, null, null] event(bf, fn() { var r = color_chooser() if (r != null) { config(d, "foreground", r) config(pf, "background", r) } }) event(bb, fn() { var r = color_chooser() if (r != null) { config(d, "background", r) config(pb, "background", r) } }) event_mouse(d, fn(a) { config(po, "text", a[1] + ", " + a[2]) if (a[0] == "PRESSED") { set(x, 0, a[1]) set(x, 1, a[2]) } if (a[0] == "RELEASED") { set(x, 2, a[1]) set(x, 3, a[2]) draw_width(d, number(get(cw, "text"))) var f = get(cf, "active") var m = get(rl, "active") if (m == true) { draw_line(d, x[0], x[1], x[2], x[3]) } var m = get(rt, "active") if (m == true) { var s = trim(get(tt, "contents")) var fa = get(co, "text") var fs = number(trim(get(ts, "contents")), font_size) config(ts, "contents", fs) if (len(s) > 0) { config(d, "font", [fa, 0, fs]) draw_string(d, s, x[0], x[1]) } } var m = get(rr, "active") if (m == true) { if (f == true) { fill_rect(d, x[0], x[1], x[2]-x[0], x[3]-x[1]) } else { draw_rect(d, x[0], x[1], x[2]-x[0], x[3]-x[1]) } } var m = get(ro, "active") if (m == true) { if (f == true) { fill_oval(d, x[0], x[1], x[2]-x[0], x[3]-x[1]) } else { draw_oval(d, x[0], x[1], x[2]-x[0], x[3]-x[1]) } } } }) event(bx, fn() { var r = number(trim(get(xr, "contents")), 0) var g = number(trim(get(xg, "contents")), 0) var b = number(trim(get(xb, "contents")), 0) each(range(0, width), fn(e, i) { each(range(0, height), fn(ee, ii) { var p = draw_get_pixel(d, e, ee) if (p != null) { var q = [p[0] + r, p[1] + g, p[2] + b] draw_set_pixel(d, e, ee, q) } }) }) }) event(br, fn() { var f = open() if (!empty(f)) { draw_read(d, f) title(name + ": " + f) } }) event(bw, fn() { var action_save = false var f = save() if (!empty(f)) { if (stat(f)["exists"] == true) { var res = confirm("File already exists. Overwrite?", "Please confirm") if (res == "OK") { var action_save = true } } else { var action_save = true } if (action_save == true) { if (draw_write_png(d, f) == true) { title(name + ": " + f) message("File saved") } } } }) show() ============================================================================= Thank you for using Singkong Programming Language.