gnscript v1.2 by G. Nagler 2021 (c) GN Midi Solutions a script language The script text is parsed into a recursive structure and interpretated without building an assembly code. The script text may be xor encrypted with gnscriptcrypt.exe tool so that the file is binary and not readable for average user. The script source may be binded with a script sum so that executing a modified script is not allowed when script sum is missing or does not match expected value. ================================================================= LEGAL ================================================================= current state of gnscript program is for demonstration and testing only. It may not be used commercially. No guarantees can be given at moment. gnscript is currently tested with Windows 7, 8, 10, 11 ================================================================= COMMENTS ================================================================= single line comments begin with // ... till end of line multi line comments begin with /* and end with next */ nested comments are not allowed: e.g. START/* // /* */END ... */ ================================================================= TOKENS ================================================================= LITERAL "...." or '....' or __multiline__("...") or __multiline__('...') or __noescape__('\[abc\]') or __escape__(...) or __singleline__(...); normal literals must not contain unencoded line breaks. multiline literals can be longer and can be more lines. backslashes and other special characters must be escaped in a literal e.g. \\ \n \r \t \x7f using __noescape__ the literal does not escape any characters and the used quote character may not be used in the literal __escape__(xxx) and __singleline__(xxx) are default if not specifying __multiline__ or __noescape__ __multiline__ and __noescape__ may be used together __multiline__(__noescape__('text over many lines')) characters are same as string literals containing only one character IDENTIFIER alphabetic character sequence that may contain underscore _ and digits inside the identifier (can not begin with digit). identifiers are case sensitive. NAMESPACEIDENTIFIER an identifier for variable or function name that may optionally start with :: or could contain namespace references e.g. ::topnamespace::subnamespace::funcname(params) namespace can be used to make a global name more specific for a certain package to avoid conflicts with system function names or global variable names. using :::: between two names is invalid namespace identifier ending with :: is invalid namespace (no "use namespace" statement is available, so if using name space then the full name space must always be used) NUMBER signed long integer number -137 hexadecimal long integer number 0x3af (digits 0-9, a-f, A-F) number with optional exponent 10e3, 10.3e-3 BOOLEAN true or false value not 0 or 0 'yes' or 'no' 'ja' or 'nein' CONSTANTS null (null object) true false pi e user applications may define more constants ================================================================= VARIABLES ================================================================= var anydata, value = -3, string='abc', vector = [1,2,3], structure = { a: 13, b: 'abc' }; 1. local variables 2. function argument variables 3. global variables 4. system variables Before assigning any value to a variable it is null and has undefined type. It gets a value and a type during assigning values. Using a variable is in most cases a runtime error. Some exceptions where using undefined variable values is allowed: operator + allows concatenating undefined values with string values (but not for adding numbers). ++ and -- operators assume value 0 for undefined variable value. == and != for comparing (possibly not initialized) variables against null isdefined(obj), isvariabledefined(obj) might be called using uninitialized or null variables Variables with same name can exist in different scopes. Variables in inner scopes hide variables of same name in outer scopes. function func(v) { // here it is v the function argument { var v = v; // local variable copies the function argument value // variable v is not the function argument } // here it is v the function argument again } ================================================================= CONSTANT VALUES FOR INITIALIZING ================================================================= var stringv = 'a b c'; functions __multiline__() and __escape__() etc. can be used to use easier syntax for entering multi line text or text with or without escape sequences like \n var number = 13; var hexnumber = 0x3a; var floatingnumber = 3.14; // 3. or .01 are not floating point numbers Hexadecimal notation in literals are never converted to numbers (they remain strings). var hexstring = '0x3a'; var vectorv = []; var vectorv = [1,'a',3..5]; // produces a vector that contains 1,'a',3,4,5 var structurev = {a: 13, b: 'xy'; c: '00.00'; 12: 'z'; ' ': 'blank'; } Structure keys are strings. when using search functions for structures comparing is string equality. ================================================================= TYPES ================================================================= null (undefined or uninitialized) string (string or any number) vector of objects (index values 0,1,2...size-1) structure of objects to objects (map) reference to an object (usually used for reference parameters in functions) user objects that are based on above types, implementation can define self if value or reference should be copied during assignment or using as parameter A variable automatically gets the type of an assigned value. A variable type can be determined using cast type operators: (string)value, (long)value, (double)value, (vector)value, (structure)value A string type expression can be also a number (long or double). It remains an unmodified string till a number operation is used: var x = '0001.0'; // remains a string as long as it is not used by a number calculation var y = x+1; // the x is converted to number 1 and adding 1 results in 2 (after this still can be used as string or as number) var x = '0001.0'; var y = (string)x+1; // x is casted to string and a 0 character is added by string concatenation. Result y is '0001.01' but can be used as string or number same as x was used. ================================================================= EXPRESSION RESULTS ================================================================= exception (error structure with message and source location where it occured: e.what, e.pos, e.linenr, e.columnnr, e.filename, e.info) null (no result) boolean true (also number != 0, 'true', 'yes', 'ja') or false (number 0, 'false', 'no', 'nein') object (can be casted to string) ============================ FUNCTION DEFINITION ============================ function functionname(param1,¶m2, ...) parameters are copied by default when calling a function. parameters with & are used by reference (not copied, any changes are also in the original scope variable). Variable arguments are proviced by l-value (not copied). The optional arguments before the variable arguments must exist and are not necessarily l-values. some functions have variable number of arguments (...), e.g. print('found ', count, ' items.'); When using variable arguments (...) parameter then a function argument name identical to the function name exists and is a vector of all parameters. function myvarargsfunc(a, ...) { var result = a; for (var i = 1; i < myvarargsfunc.size(); i++) { var parami = myvarargsfunc[i]; } return result; } ============================ CAST TYPES ============================ Following cast types can be used: long, double, float, int, string, structure, vector var x = (long)'00003.14'; // x becomes a long 3 Function gettype(obj) returns the current type of an object or expression ((long)'00003.14').gettype() returns string long (vector)null casts to empty vector (structure)null casts to empty structure Casting a structure that contains key numbers 0..n-1 to a vector results in a vector of the structure values indexed by 0..n-1 only if all index values are in the structure. e.g. {0:10,2:11,1:13} casts to vector [10,13,11] Casting an associative structure to vector results in a vector of structure elements e.g. {a:10,b:0,c:7} casts to vector [{a:10},{b:0},{c:7}] ============================ FUNCTION CALLING ============================ Functions can be called following 2 ways: funcname(param1, param2, param3); or param1.funcname(param1,param2); Both have same meaning. e.g. 3.14.sqr() == sqr(3.14) Second calling method should be used when left side is a variable. e.g. list.push(value); Available functions are only searched by name and number of the caller arguments. It does not consider their types or contents. It is not allowed to define a function with same name and same number of arguments twice. Functions can only be defined in global scope. Local functions are not supported. Function prototypes are not supported. Available functions are known directly after parsing before executing first statement. Functions are found in following order: 1. function definitions by function funcname(arguments) 2. user functions defined in user extensions in order of added extensions 3. system functions It is allowed to redefine a function that already exists as system function e.g. function min(a,b,...) { ... return myminimum; } ============================ VECTORS ============================ Vectors are arrays of a certain size indexed by a number between 0 and vector.size()-1 v.size() v[3] = value; v.push(value); // add value as last element value = v.pop(); // remove last element value = v.shift(); // remove first element v.unshift(value); // insert value as first element v.insert(index, value); // insert value at position index, if index == v.size() appends the value to end v['text'] = value produces an entry in a structure same as v{'text} = value; Hint: vector init supports constant numbers and number range and strings and character ranges e.g. var v = [3,4..7,'word','A'-'Z']; BNF syntax ========== ::= ::= [ ';' ] ::= | | | | | | | | | | | | | | | '{' '}' | | ::= 'if' '(' ')' [ 'else' ] ::= 'switch' '(' ')' '{' * '}' ::= 'case' [ '..' ] ':' | 'default' ':' | Hint: case 13..224: is a number range. case 'a'-'z': is a character range ::= 'for' '(' ';' ';' ')' ::= 'while' '(' ')' ::= 'do' '{' '}' 'while' '(' ')' Hint: repeat statements while expression is true (or break or return or exception) ::= 'repeat' '{' '}' 'until' '(' ')' Hint: repeat statements until expression is true (or break or return or exception) ::= 'foreach' '(' ['var'] IDENTIFIER 'in' ')' Hint: it is not allowed to assign values to the foreach variable. ::= 'break' ::= 'continue' ::= 'goto' IDENTIFIER ::= IDENTIFIER ':' ::= 'var' NAMESPACEIDENTIFIER [ '=' ] ::= 'undef' NAMESPACEIDENTIFIER [ '=' ] ::= 'return' [ ] ::= 'function' NAMESPACEIDENTIFIER '(' [] ')' '{' '}' ::= [ ',' ] ::= [ '&' IDENTIFIER ] ::= 'try' '{' '}' 'catch' '(' ['var'] IDENTIFIER ')' '{' '}' ::= [ ',' ] ::= [ ] ::= '=&' | '=' | '-=' | '+=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' Hint: =& is reference assignment Hint: %= is modulo Hint: &= is bit-and assignment Hint: |= is bit-or assignment Hint: ^= is bit-xor assignment Hint: <<= is bit shift left assignment Hint: >>= is bit shift right assignment ::= [ '?' ':' ] ::= [ '||' ] ::= [ '&' ] ::= [ '|' ] ::= [ '^' ] ::= [ '&' ::= [ ] ::= '==' | '!=' | '=~' | '!~' | 'in' ::= [ ] ::= '<=' | '<' | '>' | '>=' ::= [ ] >' ::= [ ] ::= '+' | '-' Hint: binary + operator prefers adding numbers than concatenating strings ::= [ ] ::= '*' | '/' | '%' ::= '+' | '-' | '!' | '~' | '++' | '--' | '(' ')' | '(' ')' | [] Hint: only ++ and -- and string + allow using undefined values. ::= 'long' | 'double' | 'float' | 'int' | 'string' | 'structure' | 'vector' ::= NUMBER | LITERAL | CONSTANT | | NAMESPACEIDENTIFIER | | functioncallstatement ::= NAMESPACEIDENTIFIER '(' functionparamlist ')' functionparamlist ::= [ ',' functionparamlist ] ::= '{' '}' ::= IDENTIFIER ':' [ structureinitlist ] ::= ',' | ';' ::= '[' ']' ::= [ '..' ] [ ',' ] ::= | | | '++' | '--' ::= '[' ']' ::= '.' IDENTIFIER | '{' '}' | '.' SYSTEM CONSTANTS true=1, false=0, null=undefined pi=3.14159265358979323846,e=2.71828182845904523536 FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_VIRTUAL, REFERENCES The default behaviour of assignment is copying the content for standard data types (string, numbers, vector, structure). The special reference assignment operator (=&) can be used to use a reference to the original object (var b =& a). User data types defined in libraries can decide that an object is to copy by reference. It can also define other behaviour for r-value copying or forbid it. This is usually used when an object is bound to internal data in an application. The structure or vector object only gives possibilities to access or modify this internal data in a common practice instead of defining functions. E.g. openfile function fileopen(...) creates a structure that can only be used as long it remains an open file structure. Therefore copying is done by reference only. var file1 = file2; E.g. a user application adds objects that can read and write to an own data structure or database. It should be avoided to copy these objects since the copied structure could be big and has not anymore the same possibilites as the user application data structure (e.g. certain functions can only be called with the original objects and not with standard vector or structure). To force to copy such an object to a standard type a compatible cast operator must be used e.g. var struct = (structure)userobj . In most cases this is not useful. FUNCTION DEFINITION function functionname(param1,&referenceparam2) functions may not be defined inside functions FUNCTION CALLING var ret = functionname(value1, variable2); or identical if value1 is an object var ret = value1.functionname(variable2); Following order is used for calling: 1. user function if defined and matching parameter number 2. user application function 3. system function system functions and user application functions support also variable arguments e.g. max(1,3,5,7,pi) By default all parameters are copied to new parameter variables. Use ¶m in the function header to use references to the original objects. Reference parameters modify values in the caller variables and speeds up calling by avoiding copying. ====================================== system functions ====================================== CODE FUNCTIONS __VERSION__() gnscript version __USEVERSION__(version) require a minimum gnscript version abort(message) abort() die(message) die() abort script optional with error message (default: empty message) exit(exitcode) exit() abort script with application exit code (default exitcode 0) throwexception(reason) sleep(milliseconds) only waits wait(milliseconds) waits while system can do some work allowmessageprocessing() shortly wait that system can do some work __LINE__() current line number __LINE__(sublevel) current line number of some caller __POS__() current source position __POS__(sublevel) current source position of some caller __FILE__() current source file name __FILE__(sublevel) current source file name of some caller __FOLDER__() current source file folder path __FOLDER__(sublevel) current source file folder path of some caller __FILE__(sublevel) current source file folder path of some caller __CODE__() current source code line __CODE__(sublevel) current source code line of some caller __CODEPOS__() current filename and line and column position and source code line __CODEPOS__(sublevel) current filename and line and column position and source code line of some caller getdebuginfo(obj) get some debug information about the obj gettype(obj) returns s type name for the value (can be null, long, double, string, vector, structure) getenv(varname) get the value of environment variable (e.g. PATH, USERNAME) setenv(varname,value) set the value of environment variable eval(code) evaluate gnscript source code using current context (variables, functions available can be called). Calling an invalid code inside a script causes an exception that can be catched with try {} catch(var e) {} callfunction(functionname, ...) calls a function by name (inner scope has priority, variable arguments are function parameters, parameter count must match with existing function) callfunction(functionheader, ...) calls a function by function header (e.g. 'myfunc(arg1,...)' inner scope has priority, variable arguments contain the function parameters. Functions with not matching parameter count are ignored, if more parameters are specified in call then they are ignored e.g. 'myfunc()' will ignore extra parameters added in call. include(filename) include a .gnscript source code from other file uselibrary(libraryname,...) load one or more dll from given path (default extension .dll is automatically added) libraries search order: absolute path name, library folders from setlibrarypath(), current folder, source code folders, interpreter application folder, system executable path currently only the main interpreter can call libraries, libraries can not use other libraries setlibrarypath(libfolder,...) add folders to library path getlibrarysummary(libraryname) get information about a loaded library uselibrary must have been called successfully gives an overview about available function headers, constants, library variables e.g. uselibrary('gnscripttestlibrary'); println(getlibrarysummary('gnscripttestlibrary')) TEXT FUNCTIONS tostring(obj) convert obj to text hexdump(obj) convert obj text to hexadecimal character numbers (can help to display binary data during debugging) translate(text, context) translate text using existing .translate files, use context for translating same text different for different purposes translate(text) translate text using existing .translate files, use global translation context strlen(text), length(text), size(text) length of text (multicharacter encoded, number of total bytes) comparetext(a,b) returns -1 if a < b or 0 if a == b or 1 if a > b comparetextignorecase(a,b) returns -1 if a < b or 0 if a == b or 1 if a > b for uppercase text a and b comparenumeric(a,b) returns -1 if a < b or 0 if a == b or 1 if a > b for numbers a and b or text a and b (if a or b is no number) getarg(varname) getarg(varname,defvalue) safe method to get variable by name, get empty string or defvalue if variable was not set for command line arguments the options can be used: -myoption1=myvalue getarg("myoption1") delivers string object "myvalue" -myoption2 getarg("myoption2") delivers boolean object true input.mid output.mid getarg("param1") delivers string input.mid and getarg("param2") delivers string output.mid without using commandline the function getarg can be used to access global variables already set by the application by before the script has started e.g. getarg("input") returns the value of global variable input using input in the script would cause a syntax error if variable input is not defined. Alternatively isvariabledefined("input") could be asked before using variable input. mid(text,pos) copy characters from position pos till end of text mid(text,pos,len) copy max len characters from position pos of text left(text,len) copy max first len characters from text right(text,len) copy max len characters from end of text fill(text, pos, size, filltext) fills all characters of text inside the given area by filltext index(text,subtext) find first matching character position of subtext inside text -1 if subtext is not found inside text containstext(text,subtext) textcontains(text,subtext) check if text contains subtext textstartswith(text,subtext) check if text begins with subtext textendswith(text,subtext) check if text ends with subtext rindex(text,subtext) find last matching character position of subtext inside text -1 if subtextis not found inside text strcmp(text1,text2) compare text1 and text2 case sensitive 0 if identical <0 if first different character of text1 is before character in text2 >0 if first different characterof text1 is after character in text2 stricmp(text1,text2) compare text1 and text2 case insensitive (A is same as a) 0 if identical <0 if first different character of text1 is before character in text2 >0 if first different characterof text1 is after character in text2 trim(text) remove spaces at beginning and end of text space character, new line, carriage return, tab character are spaces trimleft(text) remove spaces at beginning of text trimright(text) remove spaces at end of text escapestring(text) escape all special characters using \ sequences (\t for tab, \n for newline, \r for return, \\ for backslash ...) unescapestring(text) replaces escaped sequences against special characters e.g. \t against tab char \n against newline \\ against backslash ... quotestring(text) add quote characters (") before and after the text if the text contains special characters or the quote character self. unquotestring(text) if string starts with quotes " or ' then quotes are removed and text unescaped (text might contain e.g. \" as escaped character) ord(text) calculates the character number of first text character text should have length 1 isnumber(text) check if text is a number (integer or decimal) ishexnumber(text) check if text is a hexadecimal number (only uses characters 0-9 and a-f,A-F) isalpha(text) check if all text characters are ascii characters isalphanumeric(text) check if all text characters are ascii letters or digits iswhitespace(text) check if all text characters are spaces, tabs, newlines, linefeed chr(value) convert the character number to a string that contains this character toupper(text) convert all text characters to upper case (a-z to A-Z) tolower(text) convert all text characters to lower case (A-Z to a-z) split(text) split text into vector of fields assuming separator , split(text,separator) split text into vector of fields using separator splitlines(text,trimends) split text into vector of lines (newline remains at end of each line) if trimends is true all lines are trimmed at end splitwords(text) split text into a vector of words using spaces, tabs, newlines comma semicolon colon as default word separator splitwords(text,delimitercharacters) split text into a vector of words using the delimiter characters in the delimitercharacters string parameter like(text,pattern) check if text matches the pattern pattern can contain wildcards * (any characters) ? (any single character) # (any digit) join(separator,vector) join all elements in vector together to one text using separator between join(separator,text1,text2,...) join all specified text together to one text using separator between concat(vector) concatenate all vector elements together without separator concat(text1,...) concatenate all text together without separator replace(text,from,against) replace all matching from parts inside text against an other text defaultreplaceoptions() returns a structure with default replace settings used in replace(text,from,against) replace(text,pattern,replace,options) replace all matching from parts inside text against an other text options is an optional structure with optional boolean elments ignorecase, regularexpressions, wordsonly, wholeonly, replaceall, encodenotascii as delivered by function defaultreplaceoptions() parseintrangelist(text) reads a list of numbers or number ranges and writes it into a vector (e.g. 1,2 3;5-7,9 is vector [1,2,3,5,6,7,9] CSV FUNCTIONS makecsvline(stringvector,fieldseparator) build a csv line string using allowed separator characters fieldseparator separfrom the data fields in vector stringvector parsecsvline(csvline, fieldseparator) converts a valid csvline into a vector fieldseparators contains the possible csv separator characters e.g. ",;" parsecsvline(csvline) converts a valid csvline into a vector field separators can be: , ; : tab splitcsvlines(text, quotecharacters) split csv text using possible quote characters in quotecharacters to csv lines a csv line may be over multiple lines when using quote character quote characters are usually " and ' quotecharacters="\"'" splitcsvlines(text) split csv text using possible to csv lines a csv line may be over multiple lines when using quote character possible quote characters are: " and ' JSON FUNCTIONS parsejson(text) converts a text in format JSON to an object e.g. createjson(obj) converts a structure or vector or string to JSON text e.g. defaultreplaceoptions() are converted to JSON: { "encodenotascii": 0, "ignorecase": 0, "regularexpressions": 0, "replaceall": 1, "wholeonly": 0, "wordsonly": 0 } REGULAR EXPRESSION FUNCTIONS match(text,regex,...) check if text matches a regular expression regex first optional argument contains variable for the whole match next optional arguments contains variable for group match enclosed in regex using ( and ) Hint: usually the regular expression is written in a literal so the backslash characters must be escaped. Always think what should a variable after assignment contain so that this text can be used as regular expression. e.g. var wholematch, devicename; var regularexpression = "\\[device name\\]\\s*\(.*\)$"; // \[device name\]\s*(.*)$ if (line.match(regularexpression, wholematch,devicename)) { // use devicename } \\[ and \\] are required because the characters [ and ] should occur and not a regular expression character range. \\s in literal ( \s regular expression ) means any space character (space or tab or newline or linefeed) \\( would only match the character ( and not define a group substitute(text,regex,against) replace longest matching regex pattern against new text regex may reference groups using (...) against may reference groups used in regex $1 $2 ... ($0 references whole match) substitute(text,regex,against,options) replace first matching regex pattern against new text using option character list option i uses case insensitive matching (default is case sensitive) option g uses replacing all matching parts (default is replace first longest matching part) regex may reference groups using (...) against may reference groups used in regex $1 $2 ... ($0 references whole match) OBJECT FUNCTIONS isdefined(variable) isdefined(variablename) isvariabledefined(varname) checks if variable is defined isempty(obj) tests if the object is empty (can be a string or vector or structure or or null) clear(variable) set variable value empty duplicate(obj) create a copy of an object COLLECTION FUNCTIONS push(vector,value) append a copy of a value to a vector object pushreference(vector,refvalue) append a reference to a vector object pop(vector) removes an item from the back side of vector shift(vector) removes an item from the front side of vector insert(vector,index,value) inserts value at position index of vector unshift(vector,value) inserts value at front side of vector containskey(structure,key) check if structure contains key containsvalue(obj,value) check if vector or structure contains this value check if string contains value as substring removekey(structure,key) remove key from structure remove element with key index from vector removevalue(structure,value) remove structure elements matching value remove vector elements matching value remove first substring matching value from string collectionkeys(structure) get a vector of all key names from the keys used in structure collectionvalues(structure) get a vector of all values used in the structure grepkeys(structure,operator,value,...) search key names of a structure that matches specified value compares compare operations are combined using AND and evaluated from left to right compare operators are: equal: ==, =, (default: empty assumes equal operator) not equal: !=,!,<> relational: >,>=,<,<= in collection: in,!in regular expressions: =~,!~ grepvalues(collection,operator,value,...) search key values of a structure or vector that matches specified value compares compare operations are combined using AND and evaluated from left to right same operators as in grepkeys sort(vector) sort all values of a vector numerically and alphabetic sortnumeric(vector) sort all values of a vector numerical (if values are numbers),data that are not sortalphabetic(vector) sort all values of a vector alphabetic numerical are compared alphabetic ignoring case letters 23a is no number,-23,-23.4 are numbers sortignorecase(vector) sort all values of a vector alphabetically ignoring case letters A == a sort(vector,comparefunctionname,...) sort all values of a vector using an existing function with given name that has two arguments empty comparefunctionname compares numerical compare function must compare two objects and return true if the first value should be before the second value (lessthan implementation) the user function should use reference arguments lessthan(&a,&b) that unnecessary copying is avoided inversesort(vector) sort all values of a vector numeric and alphabetically in inverse order (Z to A) inversesortalphabetic(vector) sort all values of a vector alphabetically in inverse order (Z to A) inversesortnumeric(vector) sort all values of a vector numerical (if values are numbers) in inverse order (9 to 0) inversesortignorecase(vector) sort all values of a vector alphabetically ignoring case letters A == a in inverse order inversesort(vector,comparefunctionname,...) sort all values of a vector using an existing function with given name that has two arguments in inverse order empty comparefunctionname compares numerical compare function must compare two objects and return true if the first value should be before the second value (lessthan implementation) the user function should use reference arguments lessthan(&a,&b) that unnecessary copying is avoided extra parameters might be added to the sort function after the function name parameter they will be added when calling the compare function e.g. sort(v,mycompare,compareoptions); function mycompare(&a,&b,&compareoptions) { return a < b; } UNITTEST FUNCTIONS unittest_clear() reset the previous unit test results and counter unittest_count() get the number of tests called e.g. unittest_areequal(value, correctvalue, testname) this count can be used to guarantee that all unit tests of a script are really executed e.g. last unit test can be unittest_areequal(unittest_count(), 10) if the module should execute 10 unit tests unittest_results() get the failed unit test result (each line looks like unit test TESTNAME failed: reason) unittest_areequal(value, correctvalue, testname) test fails if string converted value does is not equal to string correctvalue unittest_areequalvalues(value, correctvalue, testname) test fails if long value is not equal to long correctvalue unittest_arenearvalues(value, correctvalue, maxdistance, testname) unittest_areequalbool(value, correctvalue, testname) test fails if boolean value is not equal to boolean correctvalue unittest_istrue(value, testname) test fails if value is not true unittest_isfalse(value, testname) test fails if value is not false unittest_fileexists(filepath, testname) test fails if a file at filepath is not existing (or accessible by this path) unittest_arefilecontentsequal(filepath1, filepath2, testname) test fails if one of the files do not exist or are not readable or their binary content is different. unittest_fail(message, testname) test fails always with the reason specified in message DATE/TIME FUNCTIONS timeseconds() seconds since 1.1.1970 timemilliseconds() milliseconds since last system start getdatestring() get current local date in format d.m.yyyy gettimestring() get current local time in format h:mm:ss gettimestring(seconds) format seconds into format h:mm:ss getdatetime() get current local date and time as structure containing keys: day, month, hour, minute, second OUTPUT FUNCTIONS Hint: all file operations require permission of the application that supports the operation: r=allow read file, w=allow write file, i=allow get file information sprintf(format,...) format a text that may contain formatting and optional parameters e.g. %03d (similar to C sprintf) %% is percent character %d integer numbers, %l long numbers, %s string, %c character, %f decimal numbers, %x hexadecimal numbers printf(format,...) print formatted text to standard output (commandline only) print(text,...) print text to standard output (commandline only) println(text,...) print text and newline to standard output (commandline only) println() print newline to standard output (commandline only) printerrorf(format,...) print formatted text to standard error (commandline only) printerror(format,...) print text to standard error (commandline only) printerrorln(text,...) print text and newline to standard error (commandline only) printerrorln() print newline to standard error (commandline only) fileexists(path) check if a file at path really exists filesize(path) filesize(file) get file size of file at path or of the open file equalfiles(path1,path2) check if content of two files is binary identical closedfilereadtext(path) read whole text from a file at path into one string closedfilereadlines(path) read all text lines from a file at path into a string vector closedfilereadbinary(path) read whole data from a binary file at path into a string closedfilewritebinary(path,buffer) write binary data buffer into a file at path (overwrite existing file) closedfilewritetext(path,text) write text into a text file at path (overwrite existing file) closedfileappendtext(path,text) write text into a text file at path (append to existing file) fileopen(filename) open binary file for reading only returns a file handle of open file or exception when file cannot be opened file should be closed using openfileclose(file) fileopen(filename,mode) open file according to mode mode w is write (overwrites file if existing, creates file if not existing) mode a is append (creates file if not existing) mode r is read returns a file handle of open file or exception when file cannot be opened file should be closed using openfileclose(file) openfileclose(file) close an opened file openfilepos(file) get current absolute file position of an open file openfilesize(file) get number of bytes in an open file openfileseekbegin(file) seek to beginning of an open file openfileseekbegin(file,pos) seek current position to absolute pos of an open file openfileseekcurrent(file,pos) seek current position to relative pos (can be also 0 or negative) of an open file openfileseekend(file) seek end position of an open file openfileseekend(file,pos) seek current position to a position relative to end (can be also 0 or negative) openfilechangesize(file,size) truncate or extend file size of an open file openfilereadbinary(file,size) read size bytes from an open file beginning at current file position openfilereadline(file) read a line that ends with newline or end of file from an open file beginning at current file position openfilewritebinary(file,data) write binary data to an open file at current file position openfilewritetext(file,text) write text to an open file at current file position (newlines are expanded to \r\n) copyfile(destpath, srcpath) copy file at srcpath to file at destpath (overwrite existing file) movefile(destpath, srcpath) move file at srcpath to file at destpath deletefile(path) delete file at path if existing (use system basket) deletefileforever(path) delete file at path if existing (delete forever without basket) createfilebackup(srcpath) create a backup of src file in current folder with original datetime in name createfilebackup(srcpath, destfolder) create a backup of src file in destfolder with original datetime in name isbackupfilename(srcpath) check if file name has format used by createfilebackup PATH FUNCTIONS gettemporaryfolder(...) get system temporary folder path append all specified parameters using joinpath gettemporaryfilename(nameprefix,ext) get a not existing temporary file name that uses name prefix and file extension e.g. gettemporaryfilename("begin", ".txt") the filename will get a random numbering gettemporaryfilename(ext) // e.g. gettemporaryfilename(".mid") get a not existing temporary file name that uses file extension e.g. gettemporaryfilename(".txt") the filename will get a random numbering gettemporaryfoldername(nameprefix,ext) get a not existing temporary folder name that uses name prefix and file extension e.g. gettemporaryfoldername("begin", ".txt") the filename will get a random numbering gettemporaryfoldername(ext) get a not existing temporary folder name that uses file extension e.g. gettemporaryfoldername(".txt") the filename will get a random numbering getpersonalfolder(...) getmydocumentsfolder(...) get my documents folder (is same as personal folder) append all specified parameters using joinpath getprogramfolder(...) get program folder append all specified parameters using joinpath getapplicationdatafolder(...) get application data folder append all specified parameters using joinpath joinpath(vector) append all vector elements using backslash separator joinpath(path,...) append all parameters using backslash separator (e.g. joinpath(folder, "subdirectory", "filename.ext") getfilename(path) get file name from path assumes that path is not only a folder path getdirectory(path) get folder part from path without file name getfileextension(path) get the last file extension from the path (e.g. .txt) removefileextension(path) remove the last file extension from path (e.g. .txt) quotepath(path) add quote character (") before and after the path if the path contains spaces or special characters getfullpath(shortpath) get a full path for a short path or a relative path e.g. getfullpath('..\\subdir'); readdirectory(path) get the directory entries of a folder as vector of entries each entry is a full path of a file or sub folder createdirectory(path) create folder at path createdirectories(path) create folders that build the path deletedirectory(path) delete folder at path directoryexists(path) check if folder exists at path fileattributes(path) get file attributes of path fileisnewer(path1,path2) return true if file at path1 is newer than file at path2 (exception if files do not exist or access to file information is not allowed) logmessage(text1,...) append text to global log file all specified arguments are concatenated global log file path is set by the application PROFILE FUNCTIONS writeprofilestring(inipath,section,key,value) set .ini entry key inside section to value in file inipath getprofilestring(inipath,section,key,defaultvalue) get .ini value of entry key inside section in file inipath if value is not set then use defaultvalue getprofilestring(inipath,section,key) get .ini value of entry key inside section in file inipath if value is not set then use empty string writeprofilebool(inipath,section,key,value) set .ini entry key inside section to boolean value in file inipath getprofilebool(inipath,section,key,defaultvalue) get .ini boolean value of entry key inside section in file inipath if value is not set then use defaultvalue getprofilebool(inipath,section,key) get .ini value of entry key inside section in file inipath if value is not set then use false MATHEMATICAL FUNCTIONS odd(value) check if value is odd (1,3,5,7..) round(value) round value to next integer number (for fractional part 0.5-0.999 round up, else round down) round(value,digits) round value to next decimal number that has number of digits in fractional part random(maxvalue) get a random value between 0 and maxvalue-1 random() get a random value between 0 and 65535 startrandom(initrandom) by default random() generates random numbers. Use startrandom(constantnumber) to generate (pseudo random) number list during testing (e.g. startrandom(0); random(1000000) produces 38). For unit tests random results are not so easy to verify. abs(x) -x if x is a negative number min(vector) minimum of all values in the vector min(x,y,...) minimum value of all arguments if parameter is a vector then also all the vector element values are used too e.g. min(7, [4,5,6]) results in 4 max(vector) maximum of all values in the vector max(x,y,...) maximum value of all arguments if parameter is a vector then also all the vector element values are used too e.g. max(3, [4,5,6]) results in 6 sum(x,y,...) sum value of all arguments average(x,y,...) average value of all arguments swap(&x,&y) swap content of two variables acos(x) arcus cosinus value of x asin(x) arcus sinus value of x atan(x) arcus tangens value of x atan2(x,y) angle between ray to (x,y) and positive x-axis cos(x) cosinus of value x cosh(x) hyperbolic cosinus of value x exp(x) base (constant e = 2.718) exponented by value x log(x) logarithm of value x using base e (2.718) log10(x) logarithm of value x using base 10 sin(x) sinus of value x sinh(x) hyperbolic sinus of value x tan(x) tangens of value x tanh(x) hyperbolic tangens of value x sqrt(x) square root of positive value x sqr(x) x * x ceil(x) rounding up to next higher integer number floor(x) rounding down to next lower integer number fract(x) part behind dot of value x div(x,y) integer division result of x / y modf(x) fractional part of value x fmod(x,y) floating point remainder of x / y pow(x,y) x exponented by value y (pow(2,3) = 8) BINARY BUFFER FUNCTIONS buffergetbyte(&buffer,pos) get a single byte value inside buffer at byte position pos buffersetbyte(&buffer,pos,value) set a single byte to value inside buffer at byte position pos bufferfillbytes(&buffer,pos,len,value) set next len bytes to same value inside buffer at position pos DIALOG FUNCTIONS messagebox(text, caption, boxtype) show a message box with caption and text box type can be: error, warning, information, ok, okcancel, yesno, yesnocancel, question, exclamation , abortretryignore, retrycancel or a number message box returns answers: yes, no, ok, cancel, retry, abort, ignore or a number messagebox(text, caption) show an OK message box with caption messagebox(text) show an OK message box with caption Information filechooser(initpath, caption, options, defaultext, filter) show a file chooser dialog for selecting a file or folder using caption and optional initial path options can be a text list: save, open, multi, pickfolders, overwriteprompt, pathmustexist, filemustexist default extension defaultext is added automatically when no file extension was specified in the dialog filter is a list of pairs of caption|fileextensions e.g. Images|*.gif;*.png;*.jpg|All files (*.*)|*.*|| filter must end with || filter fileextensions must be separated by ; returns selected path returns null when nothing is selected when using multi option a vector of path is returned PROCESS FUNCTIONS findapplication(appname) get the full path of an application if it is found in path, empty string if it is not found shellexecute(prog) shellexecute(prog, params) open prog with optional params in a system shell e.g. shellexecute("notepad", "file.txt"); runprocess(cmdline) execute a commandline process, returns exit code (0 is successful) runprocess(cmdline,&processoutput) execute a commandline process and store the stdout text from process in processoutput variable, return exit code (0 is successful) SOUND FUNCTIONS beep() simple system beep sound beep(soundname) sound names are simple,ok,warning,information,error,question or a number of a system beep sound playmidi(events,...) play midi events (e.g. playmidi('144 60 127');sleep(500);playmidi('144 60 0'); plays a C note for half second saytext(text) saytext(text,options) speaks text using system speech (depending on installed text to speech driver it can be language dependent or poor quality options can be usespeechsynth or usesapi (default) CLIPBOARD FUNCTIONS setclipboardtext(text) puts the text into Windows clipboard getclipboardtext() gets current Windows clipboard text (empty string if not available) GRAPHIC FUNCTIONS (still experimental) picturecreate(width,height) create an empty picture with width x height pixels picturedestroy(picture) free the picture pictureload(filename) load a picture from existing file (can be .bmp, .png, .gif, .jpg, .tif) picturesave(picture, filename, format) picturesave(picture, filename) save picture to a file (overwrites existing file) format can be "BPM", "TIFF", "JPEG", "GIF", "PNG" or empty or omitted if no format is given then file format is chosen from file extension (.bmp, .tif, .jpg, .gif or .png) or default format is "BPM". picturesavebpm(picture, filename) save picture to a .bpm file picturefillrect(picture,x,y,width,height,color) fill a rectangle part of a picture with a solid color picturedrawtext(picture,x,y,width,height,text,color) draw a colored text into a rectangular area of a picture getcolor32(alpha,r,g,b) get an rgb color with alpha channel (transparency level, 0= not transparent till 255=full transparent) getcolor24(r,g,b) get an rgb color that is not transparent getred(color) get red part of rgb color getgreen(color) get green part of rgb color getblue(color) get blue part of rgb color getalpha(color) get alpha part of transparent rgb color GNSCRIPT DEBUGGER gnscript contains an embedded debugger when started with -d (-debug) option gnscript -d filename.script The debugger stops at first source code line and shows the line. It waits for user input. help,h shows possible input commands return,r continue till end of function list,l show a list of source code lines at a given line number continue,c continue till a breakpoint stops or an exception occurs quit,q exit script stepinto,s execute next command and if it is a function call step into the function stepover,n execute next command and if it is a function call step over the function breakpoint,b set a manual a conditional breakpoint at a line variable,v set a variable change breakpoint print,p evaluate a script line and print the result on console GNSCRIPT PROFILER gnscript contains an embedded profiler when started with -profile=sortmethod The profiler counts every code line that was executed and measures the time that the execution tooks. The results are written to gnscript.log at end of script. sortmethod are: count, sumbrutto, sumnetto, singlebrutto, singlenetto (default: sumbrutto) sortmethod can start with + (ascending) or - (descending) to change the sort order (default: -, descending) This can help to optimize a slow script to find where the most time of the script is lost. Hint: in many cases slow scripts are caused by unnecessary copying of function arguments. use reference parameters (&) if possible. Hint: if a script runs very very slow look also into process task list if unnecessary processes are running (e.g. old instances of gnscript). GNSCRIPT LIMITATIONS function recursive calls are limited to a number specified by the application to prevent from possible endless recursive calls (default: 100). Allowing too many recursive calls can lead to a application stack overflow. for, while, until loops counts are limited to a number specified by the application to prevent from possible endless loop (default: 10000). using prefix and postfix operators in same expression are not allowed (postfix operator creates a r-value result that cannot be modified) GNSCRIPT INTERPRETER gnscript script "var x = 1; ++++x;" interpreter result: 3 executes the script and displays the result value (converted to string) or error exception if an error occured GNSCRIPT SCANNER gnscript -scanner script "var x = 1; ++++x;" scanner result: console L1C2 VARIABLE var console L1C6 IDENTIFIER x console L1C8 = console L1C10 NUMBER 1 console L1C11 ; console L1C13 ++ console L1C15 ++ console L1C17 IDENTIFIER x console L1C18 ; displays the scanner result token list with their source code positions or a syntax error exception if an invalid token was found GNSCRIPT PARSER gnscript -parser script "var x = 1; ++++x;" parser result: STATEMENTS{ VAR x CONSTVALUE 1 ASSIGNMENT += ASSIGNMENT += VARIABLE x CONSTVALUE 1 CONSTVALUE 1 } displays the parser result recursive grammar structure or an error exception if a syntax error was found EXTENDING GNSCRIPT LANGUAGE An application that uses gnscript library can override class GNScriptContext to extend the language. CONSTANTS own constants can be defined for own purposes e.g. EVENTTYPE_TEXT to string value 'T' FUNCTIONS the available system functions list can be extended by own functions. The own functions also support variable parameters list and return an object value (can also be a vector or structure or null) and error message if the call fails for any reason (e.g. parameters are not valid). e.g. setStatusbarText(shortmsg) LIVE VARIABLES the application may extend the language by new object type variables. Basic types can be overrided to quicklier define own structures. By default the live variables are used by reference. A clock variable could be defined and each access to the variable delivers current time. Live variables help to bind gnscript objects to existing data structures in the application. The original data is not duplicated in the script language. The original data is accessed during object usage e.g. song.event[i] = based string objects getter and setter for string value can be defined number objects getter and setter for numeric value can be defined structure field names can be defined and getter and setter for each member can be defined, remove and insert key can be defined vector getter and setter for each indexed key can be defined, remove and insert index can be defined LIBRARIES a DLL can implement a library extension. The main application (interpreter) converts calls to a library extensions to calls to dll functions. Objects are converted to global data and on dll side automatically converted back. The DLL can create own object types by implementing base classes. Such objects use the derived flag. These results objects are not copied. The interpreter remembers the class instance on the DLL side and does not hold any data copies. Everytime the DLL library must be asked and the DLL implementation can react on changes (e.g. a vector object in the library object is not visible to the interpreter but gives functions to access the vector e.g. ask vector size, ask existing vector element, pop, push, and when the interpreter does not use the object anymore it automatically calls a dll function that destroys the created object instance. GNSCRIPT UNIT TEST EXAMPLE ==== content of file goto.gnscript ============================================================================== function testjumpforward() { var x='test'; goto forwardsamelevel; x = 'wrong'; return x; forwardsamelevel: x = 'correct'; return x; } function testjumpbackward() { var x='test'; goto forwardsamelevel; return x; backwardsamelevel: x = 'correct'; return x; forwardsamelevel: goto backwardsamelevel; x = 'wrong'; return x; } function testjumpupward() { var level = 'test'; { var level = 'top'; {{ var level = 'middle'; {{ var level = 'deep'; goto jumpupward; }} jumpupward: if (level == 'middle') return 'correct'; }} } return 'wrong'; } function testwronggoto() { try { goto nowhere; } catch(var e) { return trim(e).substitute("^.*\\\\", ""); } } function testfunctionwithlabel() { print("before"); labelinfunction: print("after"); } function testgotointofunction() { try { goto labelinfunction; } catch(var e) { return trim(e).substitute("^.*\\\\", ""); } } function testjumpdownward() { try { var level = 'test'; goto downward; level = 'top'; {{{{ var newvariables= 'new'; downward: return 'wrong'; }}}} } catch(var e) { return e.substitute("^.*\\\\", ""); // warning: the pattern must be ^.*\\ and since it is in a literal we must write it "^.*\\\\" } return level; } function testdoublelabel() { try { var level = 'test'; goto doublelabel; level = 'afterfirstgoto'; doublelabel: level = 'firstgoto'; return 'wrong1'; doublelabel: level = 'secondgoto'; return 'wrong2'; } catch(var e) { return e.substitute("^.*\\\\", ""); } } function testgotoloopforever() { var n = 0; try { start: n++; goto start; } catch(var e) { return e.substitute("^.*\\\\", ""); } } unittest_areequal(testjumpforward(),'correct', "forward jump"); unittest_areequal(testjumpbackward(), 'correct', "backward jump"); unittest_areequal(testjumpupward(), 'correct', "upward jump"); unittest_areequal(testwronggoto(),'goto label not found: nowhere',"goto label not found"); unittest_areequal(testgotointofunction(), 'goto to label not allowed: labelinfunction', "goto into function"); unittest_areequal(testjumpdownward(), 'goto to label not allowed: downward', "goto label downward"); unittest_areequal(testdoublelabel(), 'label is defined twice in same scope: doublelabel', "label used twice"); unittest_areequal(testgotoloopforever(),'goto loop never ending',"goto loop forever"); unittest_areequal(unittest_count(), 8, "incomplete"); print(unittest_results()); ========================================================================================== no output if all tests successful (exit code 0) error lines if any test fails e.g. unit test incomplete failed: result=7 correct=8 testall.gnscript starts internal unit tests and all tests in subfolders fullprograms contains many example files that test itself with unit tests unit test 100-createindex.gnscript reads all the gnscript examples and creates index.txt that shows which gnscript features (e.g. functions, operators) are used in the example scripts ========================================== GNSCRIPT USAGE ========================================== gnscript 1.0 by G. Nagler (c) 2021 GN Midi Solutions usage: gnscript [options] -tests=[testnamefilter] gnscript [options] -script text gnscript [options] filename [params] options: -source show source code -lines show all breakable lines -scan show scanner token list -parse show parser tree -check check if parser errors occur -debug use console debugger -lang=xx use language translations (en, de) for translatable text -profile=xxx print times and calling counts sorted ascending or descending by method +xxx or -xxx xxx can be: count,sumbrutto,sumnetto,singlebrutto,singlenetto example command lines: > gnscript example.gnscript > gnscript -script "3+pow(2,3)" 11 > gnscript -d -script "function x(n) { return pow(2,n); } return 3+x(3)" at console L001C036 return 3+x(3) >> s at console L001C045 x(3) >> s at console L001C017 return pow(2,n) >> s at console L001C024 pow(2,n) >> s 11 > gnscript -tests 951 unit tests successful > gnscript -d example.gnscript > gnscript -profile example.gnscript > gnscript -script "var l='gnscriptdbhash';uselibrary(l); l.getlibrarysummary()'" ========================================== BUG REPORT ========================================== Please create a small .gnscript example that contains a unit test that fails on your side. It should make clear what result you expected. example bug report in file example_problem.gnscript: unittest_areequal(wrongfunction('x','y'), "expected result", __CODEPOS__() ); unittest_areequal(unittest_count(), 1, "incomplete"); print(unittest_results()); Please send a bug report by e-mail to info@gnmidi.com ================================================================ TODO ================================================================ * assignment of a variable could be l-value copy => any modification of original variable could be a problem e.g. var a = b; b++; => a still original b * not sure if ((string)'0001.0' + 1)+1 should return 0001.011 or 2.01 or 2.010000 or error * not sure if ((string)'0001.0' + 1) * 2 should return 2.020000 or error * + operator for inserting values into a vector e.g. [3,4]+5 == [3,4,5] * + operator for combining vectors e.g. [3,4]+[4,5] == [3,4,4,5] * + operator for combining structures e.g. {a:3}+{b:4} == {a:3, b:4} * == and != compares for vectors and structures (currently it works but converts to string and identical numbers could different e.g. [3.0] != [3]) * switch case with numbers e.g. 3.1 or 0007 if switch condition is not casted might be not well defined