Difference between revisions of "A Lianja Primer"

From Lianjapedia
Jump to: navigation, search
m (Admin moved page A Recital Primer to A Lianja Primer: Text replace - "Recital" to "Lianja")
(Export to Text Files)
 
(111 intermediate revisions by 3 users not shown)
Line 1: Line 1:
==A Lianja Primer==
+
{{DISPLAYTITLE: A LianjaScript Primer}}
 +
 
 +
==A LianjaScript Primer==
 +
 
 +
LianjaScript is a complete cross platform (Windows, Linux, MacOS) implementation of the Visual FoxPro scripting language and database.
 +
 
 +
It is a data centric scripting language tightly integrated in with the Lianja database engine.
 +
 
 +
It has many modern extensions to push that language forward into the future.
 +
 
 +
It is specifically designed for building Business Database Applications.
 +
 
 +
You can integrate JavaScript, Python and PHP code in with the LianjaScript/VFP code.
 +
 
 +
The database engine is extremely high performance and is fully 64 bit providing huge file support.
  
 
===Keywords===
 
===Keywords===
  
There are no reserved words in Lianja. Command names can be used as variables names. At first glance these seems strange, but provides for greater flexibility when declaring and referencing memory variables and database field variables, as you do not need to concern yourself about names that may already be used as commands.
+
There are no reserved words in LianjaScript. Command names can be used as variables names. At first glance this seems strange, but provides for greater flexibility when declaring and referencing memory variables and database field variables, as you do not need to concern yourself about names that may already be used as commands.
  
 
As an extreme example, the following code will compile and run. It will output "hello"
 
As an extreme example, the following code will compile and run. It will output "hello"
Line 19: Line 33:
 
===Lines and Indentation===
 
===Lines and Indentation===
  
Tabs and spaces have no significance in LianjaLianja commands can begin on any column of a line. A newline ends the command. If you have particularly long commands, you can them extend over multiple lines by placing the line continuation character ; (semicolon) at the end of each line that is to be continued.
+
Tabs and spaces have no significance in LianjaScriptLianjaScript commands can begin on any column of a line. A newline ends the command. If you have particularly long commands, you can extend them over multiple lines by placing the line continuation character ; (semicolon) at the end of each line that is to be continued.
  
 
<code lang="recital">
 
<code lang="recital">
Line 70: Line 84:
  
 
<code lang="recital">
 
<code lang="recital">
private x
+
private x // accessible in all called functions/procs
x = 10   // x already exists
+
local lx  // accessible only in the function/proc it is declared in
y = 10   // y does not yet exist so it is created
+
public px  // accessible globally
 +
x = 10     // x already exists
 +
y = 10     // y does not yet exist so it is created. if SET LOCAL ON is in effect (default) it is declared as a LOCAL
 +
          // otherwise a PRIVATE variable
  
 
set strict on
 
set strict on
z = 10   // error is thrown as z does not exist and STRICT is ON
+
z = 10     // error is thrown as z does not exist and STRICT is ON
 
</code>
 
</code>
  
Line 95: Line 112:
 
</code>
 
</code>
  
Variables can optionally be declared as  specific datatype.
+
Variables can optionally be declared as  a specific datatype.
  
 
<code lang="recital">
 
<code lang="recital">
 
private m_var as numeric = 1234
 
private m_var as numeric = 1234
m_var = 'a character value'    /// throws an error  
+
m_var = 'a character value'    // throws an error  
 
</code>
 
</code>
  
Line 133: Line 150:
  
 
// two-dimensional array of two rows with three columns in each row
 
// two-dimensional array of two rows with three columns in each row
private tab2 = { { "Hello", 10, date() ], { "world", 20, date()+1 } }
+
private tab2 = { { "Hello", 10, date() }, { "world", 20, date()+1 } }
  
 
// create an array on the fly
 
// create an array on the fly
Line 147: Line 164:
 
====Associative Arrays====
 
====Associative Arrays====
  
An associative array (also known as a dynamic array) is a collection of key/value pairs where the '''key''' can be used to retrieve the value. Associative arrays are '''dynamic''', meaning that elements can be added and removed dynamically.
+
An associative array (also known as a dynamic array) is a collection of key/value pairs where the '''key''' can be used to retrieve the value. Associative arrays are '''dynamic''', meaning that elements can be added and removed dynamically.  The '''key''' identifiers follow the standard naming rules (see [[#Variables|naming under Simple Variables above]]).
  
 
<code lang="recital">
 
<code lang="recital">
Line 155: Line 172:
 
tab["age"] = 25
 
tab["age"] = 25
 
tab["dob"] = date()
 
tab["dob"] = date()
 +
tab[“subobject”] = object()
 +
tab.subobject.type = “a type”
 
</code>
 
</code>
  
Line 160: Line 179:
  
 
<code lang="recital">
 
<code lang="recital">
tab = array("name" => "bill", "age" => 25, ""dob" => date())  
+
tab = array("name" => "bill", "age" => 25, "dob" => date())
 +
</code>
 +
 
 +
Associative arrays can also be created and initialized in one statement from JSON.
 +
 
 +
<code lang="recital">
 +
tab = json_decode('{"name" : "bill", "age" : 25, "dob" : date() }')
 
</code>
 
</code>
  
Line 188: Line 213:
 
object = createobject(<classname as character> [, <arg1 as expression>[, <arg2 as expression> ...]])  
 
object = createobject(<classname as character> [, <arg1 as expression>[, <arg2 as expression> ...]])  
 
</pre>
 
</pre>
 
'''Example'''
 
  
 
<code lang="recital">
 
<code lang="recital">
Line 204: Line 227:
 
// Or: myobject.removeproperty("myprop2")
 
// Or: myobject.removeproperty("myprop2")
 
</code>
 
</code>
 +
 +
====Passing arrays and objects as arguments====
 +
 +
When arrays or objects are passed as parameters to procs/functions they are always passed by reference. Similarly when they are assigned to variables, array elements or object properties the reference count is incremented.
 +
 +
This allows you to alter the array or object in the procs/function and the change is seen by all references to that array or object.
 +
 +
LianjaScript uses reference counting to prevent mishaps. Arrays and/or objects are only deleted when there are no more references to them.
 +
 +
This functionality provides a powerful solution for data analytics.
 +
 +
====Passing procs as arguments====
 +
 +
In LianjaScript a user defined proc or function can be assigned to a variable, array element or object property.
 +
 +
You can also pass a proc/function name as a parameter to your own procs/functions. This provides a powerful mechanism for handling callbacks inside your procs enabling polymorphism.
 +
 +
<code lang="recital">
 +
proc myproc(thecallback)
 +
    thecallback()
 +
endproc
 +
 +
proc mycallback()
 +
    // do something useful...
 +
endproc
 +
 +
myproc(mycallback)
 +
</code>
 +
 +
===Namespaces===
 +
 +
The [[NAMESPACE]] command declares a namespace and creates a dynamic array which can be referenced by the <namespace name>.  After declaration of a namespace, any [[PUBLIC]] variables declared are attached to that namespace, becoming members of the array.  This allows the creation of multiple [[PUBLIC]] variables with the same name, provided each belongs to a separate namespace. 
 +
 +
Issuing NAMESPACE without a <namespace name> deactivates any current namespace.
 +
 +
The currently active namespace can be determined using the NAMESPACE() function.
 +
 +
The [[CLEAR NAMESPACE]] command is used to release namespace dynamic arrays.
 +
 +
Individual public variables can be released by removing them as properties of the namespace dynamic array/object using [[REMOVEPROPERTY()]].
  
 
===Operators===
 
===Operators===
  
 
====Arithmetic operators====
 
====Arithmetic operators====
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
| +
+
|valign="top"| +
|addition
+
|valign="top"|addition
 
|returns the first value added to the second
 
|returns the first value added to the second
 
|-  
 
|-  
| -
+
|valign="top"| -
|subtraction
+
|valign="top"|subtraction
 
|returns the second value subtracted from the first
 
|returns the second value subtracted from the first
 
|-
 
|-
| *
+
|valign="top"| *
|multiplication
+
|valign="top"|multiplication
 
|returns the first value multiplied by the second
 
|returns the first value multiplied by the second
 
|-
 
|-
| *
+
|valign="top"| /
|division
+
|valign="top"|division
 
|returns the first value divided by the second
 
|returns the first value divided by the second
 
|-
 
|-
| %
+
|valign="top"| %
|modulus
+
|valign="top"|modulus
 
|returns the remainder of the first value divided by the second
 
|returns the remainder of the first value divided by the second
 
|-
 
|-
| ^
+
|valign="top"| ^
|exponential
+
|valign="top"|exponential
 
|returns the first value to the power of the second
 
|returns the first value to the power of the second
 
|-
 
|-
| **
+
|valign="top"| **
|exponential
+
|valign="top"|exponential
 
|returns the first value to the power of the second
 
|returns the first value to the power of the second
 
|-
 
|-
| +=
+
|valign="top"| +=
 
|shorthand addition
 
|shorthand addition
|adds the first value to the second
+
|valign="top"|adds the first value to the second
 
|-
 
|-
| -=
+
|valign="top"| -=
 
|shorthand subtraction
 
|shorthand subtraction
|subtracts the second value from the first
+
|valign="top"|subtracts the second value from the first
 
|-
 
|-
| *=
+
|valign="top"| *=
 
|shorthand multiplication
 
|shorthand multiplication
|multiplies the first value by the second
+
|valign="top"|multiplies the first value by the second
 
|-
 
|-
| /=
+
|valign="top"| /=
 
|shorthand division
 
|shorthand division
|divides the first value by the second
+
|valign="top"|divides the first value by the second
 
|-
 
|-
| %=
+
|valign="top"| %=
 
|shorthand modulus
 
|shorthand modulus
|divides the first value by the second and returns the remainder
+
|valign="top"|divides the first value by the second and returns the remainder
 
|}
 
|}
  
 
====Assignment operator====
 
====Assignment operator====
  
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
| =
+
|valign="top"| =
|assigment
+
|valign="top"|assigment
 
|evaluates the expression on the right hand side and stores in the target variable on the left hand side
 
|evaluates the expression on the right hand side and stores in the target variable on the left hand side
 
|-  
 
|-  
Line 277: Line 340:
 
====String operators====
 
====String operators====
  
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
| +
+
|valign="top"| +
|concatenate
+
|valign="top"|concatenate
 
|concatenates the second value to the first
 
|concatenates the second value to the first
 
|-
 
|-
| -
+
|valign="top"| -
|concatenate trimmed
+
|valign="top"|concatenate trimmed
 
|concatenates the second value to the first after trimming spaces from the end of the first
 
|concatenates the second value to the first after trimming spaces from the end of the first
 
|-
 
|-
|<nowiki>||</nowiki>
+
|valign="top"|<nowiki>||</nowiki>
|concatenate
+
|valign="top"|concatenate
 
|concatenates the second value to the first after converting to character
 
|concatenates the second value to the first after converting to character
 
|}
 
|}
Line 297: Line 360:
 
====Comparison operators====
 
====Comparison operators====
  
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
|&nbsp;=
+
|valign="top"|&nbsp;=
|equals
+
|valign="top"|equals
 
|returns true if the first value is equal to the second.  
 
|returns true if the first value is equal to the second.  
 
|-
 
|-
|&nbsp;==
+
|valign="top"|&nbsp;==
|equals
+
|valign="top"|equals
 
|returns true if the first value is exactly equal to the second or matches the pattern specified in the second value.
 
|returns true if the first value is exactly equal to the second or matches the pattern specified in the second value.
 
|-  
 
|-  
|&nbsp;!=
+
|valign="top"|&nbsp;!=
|not equals
+
|valign="top"|not equals
 
|returns true if the first value is not equal to the second
 
|returns true if the first value is not equal to the second
 
|-  
 
|-  
|&nbsp;#
+
|valign="top"|&nbsp;#
|not equals
+
|valign="top"|not equals
 
|returns true if the first value is not equal to the second
 
|returns true if the first value is not equal to the second
 
|-  
 
|-  
|&nbsp;<nowiki><></nowiki>
+
|valign="top"|&nbsp;<nowiki><></nowiki>
|not equals
+
|valign="top"|not equals
 
|returns true if the first value is not equal to the second
 
|returns true if the first value is not equal to the second
 
|-
 
|-
|&nbsp;<
+
|valign="top"|&nbsp;<
|less than
+
|valign="top"|less than
 
|returns true if the first value is less than the second
 
|returns true if the first value is less than the second
 
|-
 
|-
|&nbsp;>
+
|valign="top"|&nbsp;>
|greater than
+
|valign="top"|greater than
|returns true if the first value is greater than the second
+
|valign="top"|returns true if the first value is greater than the second
 
|-
 
|-
|&nbsp;<=
+
|valign="top"|&nbsp;<=
|less than or equal
+
|valign="top"|less than or equal
|returns true if the first value is less than or equal to the second
+
|valign="top"|returns true if the first value is less than or equal to the second
 
|-
 
|-
|&nbsp;>=
+
|valign="top"|&nbsp;>=
|less than
+
|valign="top"|less than
|returns true if the first value is greater than or equals to the second
+
|valign="top"|returns true if the first value is greater than or equals to the second
 
|-
 
|-
|&nbsp;$
+
|valign="top"|&nbsp;$
|contained in
+
|valign="top"|contained in
|returns true if the left hand side is contained in the right hand side
+
|valign="top"|returns true if the left hand side is contained in the right hand side
 
|-
 
|-
|&nbsp;<nowiki>|</nowiki>
+
|valign="top"|&nbsp;<nowiki>|</nowiki>
|contains
+
|valign="top"|contains
|returns true if the right hand side is contained in the left hand side
+
|valign="top"|returns true if the right hand side is contained in the left hand side
 
|-
 
|-
|&nbsp;<nowiki>?</nowiki>
+
|valign="top"|&nbsp;<nowiki>?</nowiki>
|sounds like
+
|valign="top"|sounds like
|returns true if the second value sounds like the first
+
|valign="top"|returns true if the second value sounds like the first
 
|}
 
|}
  
 
====Logical operators====
 
====Logical operators====
  
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
| and
+
|valign="top"| and
|logical and
+
|valign="top"|logical and
 
|returns true if first and second values are both true
 
|returns true if first and second values are both true
 
|-
 
|-
| or
+
|valign="top"| or
|logical or
+
|valign="top"|logical or
 
|returns true if either first or second values are true
 
|returns true if either first or second values are true
 
|-
 
|-
| xor
+
|valign="top"| xor
|logical xor
+
|valign="top"|logical xor
|returns true if either first or second values are true but not both
+
|valign="top"|returns true if either first or second values are true but not both
 
|-
 
|-
| not
+
|valign="top"| not
|logical not
+
|valign="top"|logical not
 
|returns the inverse if the right hand side value
 
|returns the inverse if the right hand side value
 
|-
 
|-
|&nbsp;!
+
|valign="top"|&nbsp;!
|logical not
+
|valign=top|logical not
 
|returns the inverse if the right hand side value
 
|returns the inverse if the right hand side value
 
|}
 
|}
Line 381: Line 444:
 
====Increment and Decrement operators====
 
====Increment and Decrement operators====
  
{| class="wikitable"
+
{| class="wikitable" width="100%"
!Operator
+
!width="8%"|Operator
!Type
+
!width="15%"|Type
 
!Description
 
!Description
 
|-
 
|-
| ++var
+
|valign="top"| ++var
| pre-increment
+
|valign="top"| pre-increment
| returns value after incrementing the variable
+
|valign="top"| returns value after incrementing the variable
 
|-
 
|-
| var++
+
|valign="top"| var++
| pre-increment
+
|valign="top"| post-increment
| returns current value before incrementing the variable
+
|valign="top"| returns current value before incrementing the variable
 
|-
 
|-
| --var
+
|valign="top"| --var
| pre-decrement
+
|valign="top"| pre-decrement
| returns value after decrementing the variable
+
|valign="top"| returns value after decrementing the variable
 
|-
 
|-
| var--
+
|valign="top"| var--
| post-decrement
+
|valign="top"| post-decrement
| returns current value before decrementing the variable
+
|valign="top"| returns current value before decrementing the variable
 
|}
 
|}
  
Line 409: Line 472:
 
====String constants====
 
====String constants====
  
The '''string''' type is used for textual characters. There is no type for representing just one character. A string is a series of zero or more characters. You delimit string constants using either a double-quote " or a single-quote '.
+
The '''string''' type is used for textual characters. There is no type for representing just one character. A string is a series of zero or more characters. You delimit string constants using either a double-quote " or a single-quote ', or alternatively square brackets.
  
 
<code lang="recital">
 
<code lang="recital">
 
myvar = "This is a string"
 
myvar = "This is a string"
 
myvar = 'so is this'
 
myvar = 'so is this'
 +
myvar = [this too]
 
</code>
 
</code>
  
Line 442: Line 506:
 
====Logical constants====
 
====Logical constants====
  
Logical (boolean) constants have one of two values, '''true''' or '''false'''. The result of any logical comparison expression results in a logical type.
+
Logical (boolean) constants have one of two values, '''true''' or '''false''' (or .t. and .f.). The result of any logical comparison expression results in a logical type.
  
 
<code lang="recital">
 
<code lang="recital">
Line 507: Line 571:
  
 
===Statements===
 
===Statements===
 +
 +
LianjaScript has a wide range of commands.
 +
 +
See [[:category:Commands|Commands Reference]] for a full list.
  
 
The '''statement''' is the basic unit of programming in any programming language. Statements in Lianja are delimited by a newline.
 
The '''statement''' is the basic unit of programming in any programming language. Statements in Lianja are delimited by a newline.
Line 538: Line 606:
  
 
set strict off
 
set strict off
// error as newvar does not been declared
+
// error as newvar has not been declared
 
newvar = 10
 
newvar = 10
 
</code>
 
</code>
Line 708: Line 776:
 
====Variable macro substitution====
 
====Variable macro substitution====
  
The & macro function substitutes the contents of the specified '''variable''' into the command line.  To use a macro in the middle of a word, it is necessary to end the variable name with a '.'.  Any type of memory variable can be substituted as a macro.
+
The [[&]] macro function substitutes the contents of the specified '''variable''' into the command line.  To use a macro in the middle of a word, it is necessary to end the variable name with a '.'.  Any type of memory variable can be substituted as a macro.
  
 
<code lang="recital">
 
<code lang="recital">
Line 716: Line 784:
 
         5
 
         5
 
</code>
 
</code>
 +
 +
[[&]] macro substitution is also supported in the [[Command Window]], [[Console Workspace]] and [[Console Tab]] in the App Inspector from v4.1.
  
 
====Expression macro substitution====
 
====Expression macro substitution====
  
The & macro function can also substitute the result of an expression into the command line.  The '''expression''' must be enclosed in round brackets.
+
The [[&]] macro function can also substitute the result of an expression into the command line.  The '''expression''' must be enclosed in round brackets.
  
 
<code lang="recital">
 
<code lang="recital">
Line 730: Line 800:
 
echo "&str1 &str2"    // output "hello world"
 
echo "&str1 &str2"    // output "hello world"
 
</code>
 
</code>
 +
 +
[[&]] macro substitution is also supported in the [[Command Window]], [[Console Workspace]] and [[Console Tab]] in the App Inspector from v4.1.
  
 
====Shell command output substitution====
 
====Shell command output substitution====
  
Lianja provides tight integration with the unix/linux command shell. The ` ... ` command sequence (backticks) can be used to run external shell commands that are piped together and to substitute the output into a Lianja character string.
+
Lianja provides tight integration with the Linux command shell. The ` ... ` command sequence (backticks) can be used to run external shell commands that are piped together and to substitute the output into a Lianja character string.
  
 
<code lang="recital">
 
<code lang="recital">
Line 741: Line 813:
  
 
===Functions===
 
===Functions===
 +
 +
LianjaScript has a wide range of built-in functions.
 +
 +
See [[:category:Functions|Functions Reference]] for a full list.
 +
 
====Defining a Function====
 
====Defining a Function====
 
The [[FUNCTION|function]] command is used to declare a User Defined Function (UDF).  Lianja UDFs can be used wherever a built-in Lianja function can be used.
 
The [[FUNCTION|function]] command is used to declare a User Defined Function (UDF).  Lianja UDFs can be used wherever a built-in Lianja function can be used.
Line 787: Line 864:
  
 
If the optional ADDITIVE keyword is specified then any libraries that are already open are left open and the new library is added. You can open up to 10 libraries at any one time. The [[SET PROCEDURE|set procedure to]] command, without any <filename> specified, closes all active library files.  A closed library file discards any knowledge of where the procedures within reside.  The [[CLOSE PROCEDURE|close procedure]] command provides the same functionality.  The active procedures and functions can be listed with the [[LIST PROCEDURE|list procedure]] command.
 
If the optional ADDITIVE keyword is specified then any libraries that are already open are left open and the new library is added. You can open up to 10 libraries at any one time. The [[SET PROCEDURE|set procedure to]] command, without any <filename> specified, closes all active library files.  A closed library file discards any knowledge of where the procedures within reside.  The [[CLOSE PROCEDURE|close procedure]] command provides the same functionality.  The active procedures and functions can be listed with the [[LIST PROCEDURE|list procedure]] command.
 +
 +
===Dynamically Loadable Modules===
 +
 +
Dynamically loadable modules provide Object Oriented encapsulation for LianjaScript/VFP code so that loading a library does not "pollute" the [[NAMESPACE|namespace]] and create potential problems due to name clashes.
 +
 +
Use the [[REQUIRE()|require()]] function to dynamically load a library and reference its public variables and procedures/functions in an OO manner: object.property, object.method().
 +
 +
The filename specified as the argument to the [[REQUIRE()|require()]] function must exist in the Lianja path or be prefixed with a [[Special Filename Prefixes|special prefix]] e.g. lib:/ or thirdpartylibs:/
 +
 +
<code lang="recital">
 +
// mylibrary.prg
 +
public myvar = 10
 +
proc helloworld()
 +
  ? "Hello World"
 +
endproc
 +
// end of mylibrary.prg
 +
 +
// myprog.prg
 +
local mylib = require("mylibrary.prg")
 +
? mylib
 +
? mylib.myvar
 +
mylib.helloworld()
 +
// end of myprog.prg
 +
 +
myprog()
 +
 +
// Output
 +
Dynarray (refcnt=2)
 +
(
 +
    [helloworld] => Procedure()
 +
    [myvar] => 10
 +
)
 +
        10
 +
Hello World
 +
</code>
 +
 +
=== Working with Data===
 +
 +
As the Lianja App Builder has an embedded database built into it, you do not need to install any other database software to be able to build multi-user high performance database apps for the desktop, web and mobile devices.
 +
 +
The Lianja embedded database engine is highly compatible with Visual FoxPro 9.0. It includes a feature-complete cross platform implementation of the Visual FoxPro scripting language and database engine.
 +
 +
If you know Visual FoxPro you can develop in LianjaScript leveraging all of your existing knowledge.
 +
 +
Additionally, Python, PHP and JavaScript are seamlessly integrated on top of the Lianja database engine, allowing you to build custom UI sections and custom UI gadgets in Visual FoxPro, JavaScript, PHP or Python and make full use of the power of this high performance database engine with local cursors and complete SQL and NoSQL database support.
 +
 +
To facilitate the development of custom UI sections and gadgets, Lianja comes with a cross-platform, UI independent application framework called the [[:Category:Framework Classes|Lianja UI Framework]].
 +
 +
Note that the Lianja database has a high degree of Visual FoxPro compatibility but has many extensions above and beyond Visual FoxPro to facilitate the deployment of high-availability systems with a high degree of fault tolerance.
 +
 +
If you are building a custom section in Visual FoxPro, whenever any of the methods of your section class are executed from your Lianja App, then the cursor for the table that is bound to the section will be active.
 +
 +
Accessing data in Lianja databases from Visual FoxPro is simple as this data-centric scripting language sits right on top of the Lianja database engine and everything works in a way that Visual FoxPro developers are already familiar with.
 +
 +
Note that if you are building a UI with the Lianja UI Framework, you can bind your UI controls to data sources in the Lianja database just by setting the controlsource property of the UI control to tablename.columnname, you do not need to write any special Visual FoxPro code to accomplish this.
 +
 +
====Creating a database====
 +
To create a Lianja database from LianjaScript you use the [[CREATE DATABASE]] command.
 +
 +
<code lang="recital">create database mydb</code>
 +
 +
====Opening a database====
 +
To open a Lianja database from LianjaScript you use the [[OPEN DATABASE]] command.
 +
 +
<code lang="recital">open database southwind</code>
 +
 +
====Creating a table====
 +
To create a table in a Lianja database from LianjaScript you use the [[CREATE TABLE]] command.
 +
 +
<code lang="recital">
 +
open database mydb
 +
create table mytable (firstname char(80), lastname char(80), age int)
 +
</code>
 +
 +
====Dropping a table====
 +
To drop (delete) a table from a Lianja database from LianjaScript you use the [[DROP TABLE]] command.
 +
 +
<code lang="recital">
 +
open database mydb
 +
drop table mytable
 +
</code>
 +
 +
====Create a Cursor====
 +
You can then access a table in the database using the openRecordSet() method of the [[Database]] class using SQL or NoSQL. For example we can access the customers table using SQL like this:
 +
 +
<code lang="recital">select * from customers into cursor cust</code>
 +
 +
Or alternatively just open the customers table with NoSQL like this:
 +
 +
<code lang="recital">use customers</code>
 +
 +
====Cursor data navigation====
 +
After we have opened a cursor we can navigate through the data using any of the cursor data navigation commands.
 +
 +
* [[SKIP]]
 +
* [[GOTO|GOTO TOP]]
 +
* [[GOTO|GOTO BOTTOM]]
 +
* [[SCAN]]
 +
 +
For example: to position on the first record in a cursor use the goto top command.
 +
 +
<code lang="recital">goto top</code>
 +
 +
====Extract data from the Cursor====
 +
When you are positioned on a particular record in a cursor you can extract data just by referencing the field name like this:
 +
 +
<code lang="recital">? customers.amount</code>
 +
 +
====Filtering selected data====
 +
When you open a cursor with an SQL select statement, the data selected is only that which matches the where condition of the select statement. If you open a table with NoSQL e.g.
 +
 +
<code lang="recital">use customers</code>
 +
 +
You can filter the records that are returned using the data restriction commands.
 +
 +
* [[SET FILTER]]
 +
* [[SCAN]]
 +
 +
For example to lookup a customer by their id and scan through the data selecting only those records that satisfy a certain condition you could write:
 +
 +
<code lang="recital">use customers order tag id
 +
seek "12345"
 +
scan rest while amount > 0
 +
  ? "Amount is ", customers.amount
 +
endscan</code>
 +
 +
The [[SET RUSHMORE]] command enables or disables Rushmore-style (Visual FoxPro) index scanning optimization for NoSQL and SQL commands. SET RUSHMORE is ON by default. This reduces coding extensively.
 +
 +
<code lang="recital">
 +
use customers
 +
scan for id="12345" and amount > 0
 +
  ? "Amount is ", customers.amount
 +
endscan
 +
</code>
 +
 +
====NoSQL keyed data access====
 +
When opening a table in NoSQL mode, you can lookup records by keys.
 +
 +
<code lang="recital">use customers order tag id
 +
seek "12345"
 +
if not found()
 +
  // key was not found.
 +
endif</code>
 +
 +
====Adding new records====
 +
You can add new blank records to a recordset using the [[APPEND BLANK]] command.
 +
 +
<code lang="recital">use customers
 +
append blank
 +
replace id with "34567", amount with 0</code>
 +
 +
Or alternatively the [[SQL INSERT]] command.
 +
 +
<code lang="recital">insert into customers (id, amount) values("34567", 0)</code>
 +
 +
Multiple records can be inserted in one statement.
 +
 +
<code lang="recital">
 +
insert into southwind!example (account_no, last_name, first_name) values ;   
 +
("20000", "Smith", "Sam"), ;   
 +
("20001", "Jones", "Jack"), ;   
 +
("20002", "Baker", "Beryl")
 +
</code>
 +
 +
Note that after executing append blank the record is not written until the you move the record pointer as Lianja supports record buffering by default. This allows you to update the fields of the blank record prior to it being committed into the table.
 +
 +
====Updating records====
 +
You can update records in a cursor using the [[REPLACE]] command.
 +
 +
<code lang="recital">use customers
 +
set order to tag id
 +
seek "12345"
 +
if found()
 +
  replace amount with amount+1000
 +
endif</code>
 +
 +
Or alternatively using the [[SQL UPDATE]] command.
 +
 +
<code lang="recital">update customers set amount = amount+1000  where id = "12345"</code>
 +
 +
====Deleting records====
 +
You can delete records in a cursor using the [[DELETE]] command.
 +
 +
<code lang="recital">use customers
 +
set order to tag id
 +
seek "12345"
 +
if found()
 +
  delete
 +
endif</code>
 +
 +
Or alternatively using the [[SQL DELETE]] command.
 +
 +
<code lang="recital">delete from customers  where id = "12345"</code>
 +
 +
====Closing a Cursor====
 +
You close a cursor using the [[USE]] command.
 +
 +
<code lang="recital">select customers
 +
use</code>
 +
 +
====Closing a Database====
 +
You close a database using the [[CLOSE DATABASE]] command.
 +
 +
<code lang="recital">close database</code>
 +
 +
====Datasessions====
 +
 +
If you are calling a function/proc that may interfere with the data session state (tables open, position in cursors etc), use the PUSH and POP DATASESSION commands to save and restore the state.
 +
 +
<code lang="recital">
 +
// myproc.prg
 +
push datasession
 +
open database someotherdb
 +
use accounts
 +
//...
 +
pop datasession
 +
</code>
 +
 +
===Importing and Exporting Data===
 +
The Navigational Data Access commands [[APPEND FROM|append from]] and [[COPY|copy to]] can respectively be used to import and export Lianja data.
 +
 +
The SQL statements [[CREATE TABLE|create table]] and [[INSERT|insert]] allow data to be imported from XML files and the ''into'', ''save as'' and ''to'' clauses supported by the [[SQL SELECT|SQL select]] statement offer a wide range of export formats for the selected rows.
 +
 +
====Import From Text Files====
 +
 +
The [[APPEND FROM|append from]] command imports data into the currently [[SELECT|selected]] table from the specified file of the stated format.  The file extension of the imported file is assumed to be ''.csv'' for type csv and ''.txt'' for all other types unless stated.
 +
 +
<pre>
 +
append from <filename>
 +
[while <condition as logical>] [for <condition as logical>]
 +
[type] sdf | fixed | delimited | delimited with blank | delimited with <delimiter> | csv
 +
</pre>
 +
 +
<code lang="recital">
 +
use example
 +
// Standard data file
 +
append from custsdf sdf
 +
// Fixed length fields
 +
append from custfix fixed
 +
// Delimited (',' separated, "" around character fields)
 +
append from custdel delimited
 +
// Delimited with blank (single space separates fields)
 +
append from custblank delimited with blank
 +
// Delimited with <delimiter> (',' separated, <delimiter> around character fields)
 +
append from custpipe delimited with |
 +
// Microsoft Excel compatible comma-separated
 +
append from custcsv csv
 +
</code>
 +
 +
====Import from XML====
 +
The [[CREATE TABLE|SQL create table]] statement allows a new table to be created from metadata stored in an XML file and the data to be optionally loaded.
 +
 +
<pre>
 +
create table <table>
 +
  [from] xml <.xml file> [load]
 +
</pre>
 +
 +
<code lang="recital">
 +
create table customer2;
 +
  from xml cust.xml load
 +
</code>
 +
 +
The [[SQL INSERT|SQL insert]] statement supports the import of data from an XML file:
 +
 +
<pre>
 +
insert into <table> from [xml] <xml filename>
 +
</pre>
 +
 +
<code lang="recital">
 +
insert into products;
 +
  from xml prices.xml
 +
</code>
 +
 +
====Export to Text Files====
 +
The [[COPY|copy to]] command exports data from the currently [[SELECT|selected]] table to the specified file in the stated format.  The file extension of the exported file is assumed to be ''.csv'' for type csv and ''.txt'' for all other types unless included.
 +
 +
<pre>
 +
copy to <filename>
 +
[while <condition as logical>] [for <condition as logical>]
 +
[type] sdf | fixed | delimited | delimited with blank | delimited with <delimiter> | csv
 +
</pre>
 +
 +
<code lang="recital">
 +
use example
 +
// Standard data file
 +
copy to custsdf sdf
 +
// Fixed length fields
 +
copy to custfix fixed
 +
// Delimited (',' separated, "" around character fields)
 +
copy to custdel delimited
 +
// Delimited with blank (single space separates fields)
 +
copy to custblank delimited with blank
 +
// Delimited with <delimiter> (',' separated, <delimiter> around character fields)
 +
copy to custpipe delimited with |
 +
// Microsoft Excel compatible comma-separated
 +
copy to custcsv csv
 +
</code>
 +
 +
The [[SQL SELECT|SQL select]] statement can export the selected data in text file format.
 +
 +
<pre>
 +
select <column-definition> from <table-definition> to file <filename>
 +
</pre>
 +
 +
<code lang="recital">
 +
select * from shippers to file shiptxt
 +
</code>
 +
 +
====Export to XML====
 +
The [[COPY|copy to]] command can export data from the currently [[SELECT|selected]] table to an XML file.  The file extension of the exported file is assumed to be ''.xml'' unless included.
 +
 +
<pre>
 +
copy to <filename>
 +
[while <condition as logical>] [for <condition as logical>]
 +
[type] xml
 +
</pre>
 +
 +
<code lang="recital">
 +
use example
 +
copy to custxml xml
 +
</code>
 +
 +
The [[SQL SELECT|SQL select]] statement can export the selected data in XML file format.
 +
 +
<pre>
 +
select <column-definition> from <table-definition> into xml <xml filename>
 +
</pre>
 +
 +
or
 +
 +
<pre>
 +
select <column-definition> from <table-definition> save as xml <xml filename>
 +
</pre>
 +
 +
<code lang="recital">
 +
select employeeid, lastname, firstname;
 +
  from employees into xml emp
 +
select employeeid, lastname, firstname;
 +
  from employees save as xml emp
 +
</code>
 +
 +
====Export to HTML====
 +
The [[SQL SELECT|SQL select]] statement can export the selected data in HTML file format.
 +
 +
<pre>
 +
select <column-definition> from <table-definition> into html <html filename>
 +
</pre>
 +
 +
<code lang="recital">
 +
select employeeid, lastname, firstname;
 +
  from employees into html emp
 +
</code>
 +
 +
====Export to JSON====
 +
The [[SQL SELECT|SQL select]] statement can export the selected data in JSON file format.
 +
 +
<pre>
 +
select <column-definition> from <table-definition> into json <filename> [memobase64]
 +
</pre>
 +
 +
<code lang="recital">
 +
select employeeid, lastname, firstname;
 +
  from employees into json emp
 +
</code>
 +
 +
===Integrating Python with LianjaScript===
 +
In LianjaScript you can execute any Python code using the built-in [[EXECPYTHON()|execPython()]] function.
 +
 +
<code lang="python">
 +
# file: barrytest.py
 +
import Lianja
 +
import sys
 +
 +
def myfunc(theArg):
 +
    Lianja.writeOutput("myfunc was called with " + theArg)
 +
    return Lianja.evaluate("now()")
 +
 +
if len(sys.argv) > 0:
 +
    global returnvalue
 +
    returnvalue = myfunc( sys.argv[1] )
 +
</code>
 +
 +
You can dynamically load this library (once) and call it in one statement.
 +
 +
<code lang="recital">
 +
result = execPython("barrytest::myfunc('hello world')")
 +
</code>
 +
 +
Inside your Python code you can execute any LianjaScript code using [[Lianja_Methods#Commands_and_Scripts|Lianja.evaluate()]] and [[Lianja_Methods#Commands_and_Scripts|Lianja.execute()]].
 +
 +
<code lang="python">
 +
now = Lianja.evaluate("now()")
 +
</code>
 +
 +
The argument to Lianja.evaluate() can contain user defined functions and procedure calls. This provides full two way integration between LianjaScript and Python.
 +
 +
<code lang="python">
 +
Lianja.evaluate("yourLianjaFunction('hello', 'world')")
 +
</code>
 +
 +
This tight integration let's you use any of the Python AI, ML and other useful modules available and use them in LianjaScript.
 +
 +
Full details can found in the article [[Integrating_Python_with_LianjaScript|Integrating Python with LianjaScript]].
 +
 +
===Building a UI===
 +
 +
You normally build the UI of your App visually in the page builder. You can then specify delegate procs written in LianjaScript in the [[Page_Builder_Assistant|Page Builder Assistant]] or the [[App_Inspector|App Inspector]]. These delegates respond to events such as a ''click'' or an ''interactivechange'' of text in a textbox or other UI control.
 +
 +
You can also develop custom UI code written in LianjaScript using the [[:Category:Framework_Classes|Ui Framework Classes]]. e.g.
 +
 +
<code lang="recital">
 +
local m_form = createObject("form")
 +
m_form.addObject("m_cont", "container")
 +
m_cont.backcolor = "lightgreen"
 +
m_form.resize(600, 400)
 +
m_form.show(1)
 +
</code>
  
 
[[Category:Lianja Scripting Essentials]]
 
[[Category:Lianja Scripting Essentials]]

Latest revision as of 20:58, 24 May 2024


Contents

A LianjaScript Primer

LianjaScript is a complete cross platform (Windows, Linux, MacOS) implementation of the Visual FoxPro scripting language and database.

It is a data centric scripting language tightly integrated in with the Lianja database engine.

It has many modern extensions to push that language forward into the future.

It is specifically designed for building Business Database Applications.

You can integrate JavaScript, Python and PHP code in with the LianjaScript/VFP code.

The database engine is extremely high performance and is fully 64 bit providing huge file support.

Keywords

There are no reserved words in LianjaScript. Command names can be used as variables names. At first glance this seems strange, but provides for greater flexibility when declaring and referencing memory variables and database field variables, as you do not need to concern yourself about names that may already be used as commands.

As an extreme example, the following code will compile and run. It will output "hello"

procedure if(if)
return if
 
if = "hello"
if if = "hello"
    echo if( if )
endif

Lines and Indentation

Tabs and spaces have no significance in LianjaScript. LianjaScript commands can begin on any column of a line. A newline ends the command. If you have particularly long commands, you can extend them over multiple lines by placing the line continuation character ; (semicolon) at the end of each line that is to be continued.

echo "This is a one line command"
echo "This is a ;
multi line ;
command"

For better code readability it is recommended that you indent code blocks such as if statements, for loops etc.

// indented code if much more readable and easier to maintain
for i=1 to 10
    name = "hello world"
    if name = "hello world"
        // indent like this
    endif
endfor

Comments

Single line comments

// allows comment lines to be inserted in programs to enhance their readability and maintainability. The // command allows all characters following it on a line, to be treated as a comment and to be ignored by Lianja. The // command can be placed anywhere on a line, even following an executable command.

// declare variables
private x,y,z

Multi line comments

/* and */ denote block comments. These can be inserted in programs to enhance their readability and maintainability.

The /* denotes the start of the comment block, the */ the end of the comment block.

All characters between the two comment block delimiters are treated as comments and ignored by Lianja.

/* the following lines
     are multi
     line comments */
private x,y,z

Variables

Variables in Lianja do not need to be explicitly declared, although they should be for better code readability and maintainability. When an expression is assigned to a variable, if the variable does not already exist then it will be created implicitly unless SET STRICT ON is in effect.

private x  // accessible in all called functions/procs
local lx   // accessible only in the function/proc it is declared in
public px  // accessible globally
x = 10     // x already exists
y = 10     // y does not yet exist so it is created. if SET LOCAL ON is in effect (default) it is declared as a LOCAL 
           // otherwise a PRIVATE variable
 
set strict on
z = 10     // error is thrown as z does not exist and STRICT is ON

Simple Variables

Variable names must begin with a letter (A-Z, a-z) or an underscore (-), followed by any combination of letters, digits or underscores. The variable name can be of any length, but only the first 32 characters are significant, so these must be unique. Lianja ignores the case of letters, so m_var, M_VAR, and m_VaR would all be treated as the same memory variable name. The name given to a variable has no bearing on the type of data that is, or can be, stored in it. In fact, the type of data stored in a particular variable can be changed at any time unless SET STRICT is ON, in which case Lianja will type check variables on assigment to them.

m_var = 1234
m_var = 'a character value'
? m_var + 100

Variables can be declared and optionally initialized before used.

private m_var = 1234
m_var = 'a character value'
? m_var + 100

Variables can optionally be declared as a specific datatype.

private m_var as numeric = 1234
m_var = 'a character value'    // throws an error

Static Arrays

A static array is an ordered list of elements (variables) that is of a fixed size (number of elements). You declare a static array by specifying the number of elements when you declare a variable.

private tab[ 20 ]    // declare a static array of 20 elements all initialized to False
 
// iterate through the array (note the use of the alen( ) function to find the length of the array
for i=1 to alen( tab )
    // change each array element to hold a numeric value
    tab[ i ] = i
endfor

You can initialize a static array with one statement.

// declare the array and init all elements to false
declare tab[10, 10]
 
// init all elements to zero
tab = 0

You can create and initialize static arrays using static array initializers.

// simple one dimensional array with 2 elements
private tab = { "Hello", "world" }
 
// two-dimensional array of two rows with three columns in each row
private tab2 = { { "Hello", 10, date() }, { "world", 20, date()+1 } }
 
// create an array on the fly
mytab = { 10, 20, 30, 40, 50, 60 }

You can view the contents of a static array using the echo or ? commands.

? tab

Associative Arrays

An associative array (also known as a dynamic array) is a collection of key/value pairs where the key can be used to retrieve the value. Associative arrays are dynamic, meaning that elements can be added and removed dynamically. The key identifiers follow the standard naming rules (see naming under Simple Variables above).

private tab[]    // note the use of [] to denote a dynamic array
 
tab["name"] = "bill"
tab["age"] = 25
tab["dob"] = date()
tab[“subobject”] = object()
tab.subobject.type = “a type

Associative arrays can be created and initialized in one statement using the array( ) function.

tab = array("name" => "bill", "age" => 25, "dob" => date())

Associative arrays can also be created and initialized in one statement from JSON.

tab = json_decode('{"name" : "bill", "age" : 25, "dob" : date() }')

You can view the contents of an associative array using the echo or ? commands.

? tab

Objects and Classes

The define class command is used to define a class. Within the define class...enddefine block all aspects of the class – its name, events, methods and properties can be specified.

define class <classname as character> [as <baseclass as character> | custom]
[<propertyname1 as character>[, <propertyName2 as character> ...]]
[<object>.]<propertyname> = <value as expression> ...]
[add object <objectname as character> as <baseclass as character> [with <property-list>]]
[function | procedure <procname as character>[_access | _assign]
  <command statements>
[endfunc | endproc]] 
enddefine

The createobject() function is used to create an object based on a defined class.

object = createobject(<classname as character> [, <arg1 as expression>[, <arg2 as expression> ...]]) 
define class myclass as custom
  myprop = "Hello World"
enddefine
 
myobject = createobject("myclass")
Messagebox(myobject.myprop)
addproperty(myobject, "myprop2", "goodbye")
// Or: myobject.addproperty("myprop2", "goodbye")
Messagebox(myobject.myprop2)
removeproperty(myobject, "myprop2")
// Or: myobject.removeproperty("myprop2")

Passing arrays and objects as arguments

When arrays or objects are passed as parameters to procs/functions they are always passed by reference. Similarly when they are assigned to variables, array elements or object properties the reference count is incremented.

This allows you to alter the array or object in the procs/function and the change is seen by all references to that array or object.

LianjaScript uses reference counting to prevent mishaps. Arrays and/or objects are only deleted when there are no more references to them.

This functionality provides a powerful solution for data analytics.

Passing procs as arguments

In LianjaScript a user defined proc or function can be assigned to a variable, array element or object property.

You can also pass a proc/function name as a parameter to your own procs/functions. This provides a powerful mechanism for handling callbacks inside your procs enabling polymorphism.

proc myproc(thecallback)
    thecallback()
endproc
 
proc mycallback()
    // do something useful...
endproc
 
myproc(mycallback)

Namespaces

The NAMESPACE command declares a namespace and creates a dynamic array which can be referenced by the <namespace name>. After declaration of a namespace, any PUBLIC variables declared are attached to that namespace, becoming members of the array. This allows the creation of multiple PUBLIC variables with the same name, provided each belongs to a separate namespace.

Issuing NAMESPACE without a <namespace name> deactivates any current namespace.

The currently active namespace can be determined using the NAMESPACE() function.

The CLEAR NAMESPACE command is used to release namespace dynamic arrays.

Individual public variables can be released by removing them as properties of the namespace dynamic array/object using REMOVEPROPERTY().

Operators

Arithmetic operators

Operator Type Description
+ addition returns the first value added to the second
- subtraction returns the second value subtracted from the first
* multiplication returns the first value multiplied by the second
/ division returns the first value divided by the second
 % modulus returns the remainder of the first value divided by the second
^ exponential returns the first value to the power of the second
** exponential returns the first value to the power of the second
+= shorthand addition adds the first value to the second
-= shorthand subtraction subtracts the second value from the first
*= shorthand multiplication multiplies the first value by the second
/= shorthand division divides the first value by the second
 %= shorthand modulus divides the first value by the second and returns the remainder

Assignment operator

Operator Type Description
= assigment evaluates the expression on the right hand side and stores in the target variable on the left hand side

String operators

Operator Type Description
+ concatenate concatenates the second value to the first
- concatenate trimmed concatenates the second value to the first after trimming spaces from the end of the first
|| concatenate concatenates the second value to the first after converting to character

Comparison operators

Operator Type Description
 = equals returns true if the first value is equal to the second.
 == equals returns true if the first value is exactly equal to the second or matches the pattern specified in the second value.
 != not equals returns true if the first value is not equal to the second
 # not equals returns true if the first value is not equal to the second
 <> not equals returns true if the first value is not equal to the second
 < less than returns true if the first value is less than the second
 > greater than returns true if the first value is greater than the second
 <= less than or equal returns true if the first value is less than or equal to the second
 >= less than returns true if the first value is greater than or equals to the second
 $ contained in returns true if the left hand side is contained in the right hand side
 | contains returns true if the right hand side is contained in the left hand side
 ? sounds like returns true if the second value sounds like the first

Logical operators

Operator Type Description
and logical and returns true if first and second values are both true
or logical or returns true if either first or second values are true
xor logical xor returns true if either first or second values are true but not both
not logical not returns the inverse if the right hand side value
 ! logical not returns the inverse if the right hand side value

Increment and Decrement operators

Operator Type Description
++var pre-increment returns value after incrementing the variable
var++ post-increment returns current value before incrementing the variable
--var pre-decrement returns value after decrementing the variable
var-- post-decrement returns current value before decrementing the variable

Constants

Variables each have a type determined by the data they contain. You can initialize variables by assigning constants into them. Constants each have a primitive type and are the building blocks for all data in Lianja

String constants

The string type is used for textual characters. There is no type for representing just one character. A string is a series of zero or more characters. You delimit string constants using either a double-quote " or a single-quote ', or alternatively square brackets.

myvar = "This is a string"
myvar = 'so is this'
myvar = [this too]

Numeric constants

Numeric types in Lianja are all stored as floating point numbers with zero or more decimal places.

myvar = 10
myvar = 10.5678

You can also define hexadecimal numeric constants.

myvar = 0x0a

Date constants

Date constants are delimited by curly braces.

// set myvar to 10th of october 2009
myvar = {10/10/2009}

Logical constants

Logical (boolean) constants have one of two values, true or false (or .t. and .f.). The result of any logical comparison expression results in a logical type.

myvar = true
myvar2 = false
if myvar or myvar2
    // this will be executed as myvar is true
endif

Currency constants

Currency constants are preceeded by a $.

// set myvar to 1000 dollars
myvar = $1000

Datetime constants

Datetime constants are delimited by curly braces.

// set myvar to 10th of october 2009 at 8.30am
myvar = {10/10/2009 08:30}

Expressions

Expressions and Statements are the building blocks of Lianja programs.

Expressions consists of combinations of operands which can be constant values such as strings, numbers, and dates and operands which are symbols that act on these operands in some way e.g. add two numbers together, concatenate two strings etc.

hello = "Hello"
world = "world"
hello_world = hello + " " + world
 
result = 100 * 500 / 2 - 6

Operator precedence in expressions

When you have multiple expression terms as in this example:

result = 100 * 500 / 2 - 6

As a general guideline, the operations are performed in the following order.

  • Expressions are evaluated from left to right
  • Multiplication, division and exponents are performed before addition and subtraction
  • Expressions in parentheses are evaluated first
  • Mathematical expressions are evaluated before boolean expressions (AND, OR, NOT, XOR)

If in doubt always use parentheses for readability:

result = ((100 * 500) / 2) - 6

Statements

LianjaScript has a wide range of commands.

See Commands Reference for a full list.

The statement is the basic unit of programming in any programming language. Statements in Lianja are delimited by a newline.

echo "Hello world"

You can extend statements over multiple lines by placing a ';' at the end of the line:

echo "Hello ;
world" + ;
" this is a multi-line statement"

Assigment Statements

The assignment statement stores the result of the expression expression into a variable.

variable = expression

If the variable does not exist and STRICT is OFF, then it is created. If the variable already exists, its contents are updated. If the variable does not exist (has not been declared) and STRICT is ON, then an error is thrown. When STRICT is ON, you should pre-declare variables before assigning values to them using the private, public or local commands.

private myvar
 
set strict on
// no error as myvar has already been declared
myvar = "hello world"
 
set strict off
// error as newvar has not been declared
newvar = 10

You can declare and initialize variables in one statement

private myvar = "hello world today is " + cdow( date() )

Lianja automatically performs type conversions for variables. If, for example, an existing variable called name contains a character string, and the command name=10 is executed, the variable will automatically be converted to a numeric variable.

If you explicitly tell Lianja what type of data can be stored in a variable, it will perform data type checking at runtime.

private myvar as character = "hello world today is " + cdow( date() )
 
// an error will be thrown because myvar was declared as a character
myvar = 10

Control flow statements

The IF command

The if ... endif command is how basic decisions are made in Lianja. The if command has a condition and a code body. If the condition evaluates to true then the code body is executed.

name = "bill"
if name = "bill"
    echo "name is bill"
endif

The if ... else ... endif command allows for two-way control flow.

name = "bill"
if name = "bill"
    echo "name is bill"
else
    echo "name is not bill"
endif

The if ... elseif ... endif command allows for multi-way control flow.

name = "bill"
if name = "bill"
    echo "name is bill"
elseif name = "tom"
    echo "name is tom"
elseif name = "mike"
    echo "name is mike"
else
    echo "unknown name"
endif
The DO CASE command

The DO CASE command selects one course of action out of many alternatives. Lianja evaluates each CASE condition in turn. As soon as one of the conditions evaluates to true the code body for that CASE is executed and any further case statements are ignored. Following execution of the code body, the program continues after the ENDCASE statement.

OTHERWISE If an OTHERWISE statement is present and no CASE condition evaluates to true the OTHERWISE code body is executed.

ENDCASE If no CASE condition is true and there is no OTHERWISE statement specified, then control skips to the next command following the ENDCASE.

CASE statements, as with all of the other Lianja statements can be nested. In other words, a CASE statement can contain further DO CASE commands.

do case
    case upper(command) = "BROWSE"
        echo command
    case upper(command) = "DIR"
        echo command
    otherwise
        echo "Unknown command."
endcase

Looping statements

DO WHILE statement

The DO WHILE command repeats the commands between the DO WHILE and the ENDDO statement, until a specified condition becomes false.

do while condition
    // code body
enddo

If the specified condition is true, then all commands within the DO WHILE loop will be executed. If the specified condition is false, then the first statement following the ENDDO will be executed.

If an EXIT statement is encountered then the DO WHILE loop is exited.

If a LOOP statement is encountered, then control returns to the head of the DO WHILE loop.

Statements within the DO WHILE must be properly nested.

// scan through a database table displaying information about an event
use events
seek "OPERA"
do while event = "OPERA"
    echo event, seats*price
    skip
enddo
FOR statement
for var = startvalue to endvalue [step stepvalue]
    // commands
endfor

The FOR ... ENDFOR command repeats the commands between the FOR and the ENDFOR statement.

The startvalue specifies the loop start point and endvalue the loop end point. These may be numeric or date values.

The FOR...ENDFOR command is equivalent to a counter based DO WHILE ... ENDDO set of commands but FOR ... NEXT is faster.

If the optional STEP stepvalue is specified, then the FOR ... ENDFOR loop will increment by stepvalue. This value can be a positive or negative number. If stepvalue is not specified then the FOR ... ENDFOR loop will increment by 1.

The looping will continue until either endvalue is reached or an EXIT command is encountered.

If a LOOP command is encountered, then control returns to the start of the FOR ... ENDFOR loop.

for i = 1 to 10 step 2
    if i % 1 = 1
        loop
    endif
    echo i*2
endfor
FOREACH statement

The FOREACH command simply gives an easy way to iterate over arrays. FOREACH works on arrays and objects, and will issue an error when you try to use it on a variable with a different data type or an uninitialized variable.

There are two syntaxes; the second is a minor but useful extension of the first:

// static array
private myarray = { "hello", "world" }
 
foreach myarray as value
    echo value
endfor
 
// associative array
private myarray = array("Name" => "Lianja", "Description" => "database")
foreach myarray as key => value
    echo "key=" + key + "value=" + value
endfor

The first form loops over the array myarray. On each loop, the value of the current element is assigned to value and the internal array pointer is advanced by one (so on the next loop, you'll be looking at the next element).

The second form does the same thing, except that the current element's key will be assigned to the variable key on each loop. This form works only on associative arrays and objects.

Macros

Variable macro substitution

The & macro function substitutes the contents of the specified variable into the command line. To use a macro in the middle of a word, it is necessary to end the variable name with a '.'. Any type of memory variable can be substituted as a macro.

subscript = 10
i10i = 5
? i&subscript.i
         5

& macro substitution is also supported in the Command Window, Console Workspace and Console Tab in the App Inspector from v4.1.

Expression macro substitution

The & macro function can also substitute the result of an expression into the command line. The expression must be enclosed in round brackets.

subscript = "1"
i10i = 5
? i&(subscript + "0")i
         5
str1 = "hello"
str2 = "world"
echo "&str1 &str2"    // output "hello world"

& macro substitution is also supported in the Command Window, Console Workspace and Console Tab in the App Inspector from v4.1.

Shell command output substitution

Lianja provides tight integration with the Linux command shell. The ` ... ` command sequence (backticks) can be used to run external shell commands that are piped together and to substitute the output into a Lianja character string.

echo "The default directory is `pwd`"
echo "There are `ls -l *.dbf | wc -l` tables in this directory"

Functions

LianjaScript has a wide range of built-in functions.

See Functions Reference for a full list.

Defining a Function

The function command is used to declare a User Defined Function (UDF). Lianja UDFs can be used wherever a built-in Lianja function can be used.

A UDF can have a variable number of parameters passed to it. These are assigned to private variables in the the <parameter-list> declaration or parameters statement. The parameters() function can be used to determine how many actual parameters were specified.

The function command is terminated with an endfunc or return statement.

function <name as character>[(<parameters as list>)]
[parameters <parameters as list>]
[return <value as expression> | endfunc]

Calling a Function

Functions can be included in program files, as well as in procedure library files. The functions in a procedure library file are made known to the Lianja process by using the set procedure command.

set procedure to [<filename as character> [ADDITIVE]] 

Functions can be called like built-in functions: postfixing the name of the function with brackets containing any arguments, e.g.

myudf(m_var,"Hello World",123.45,{12/03/2010})

In this case, parameters are passed by value: a copy of the memory variable is passed to the module and the original memory variable is not accessible within the called module.

Alternatively, the function can be called using the do command and specifying the arguments in the with clause, e.g.

do myudf with m_var,"Hello World",123.45,{12/03/2010}

With do command, parameters are passed by reference: the called module is given the address of the memory variable so the memory variable itself can be altered.

Libraries

Libraries of shareable procedures and functions can be accessed using the set procedure command as described above.

set procedure to [<filename as character> [ADDITIVE]] 

The set procedure command opens the specified library file, scans the contents of it, and records the names and positions of the procedures and functions defined within it. You can place as many procedures and functions as you want in a procedure library file.

If the optional ADDITIVE keyword is specified then any libraries that are already open are left open and the new library is added. You can open up to 10 libraries at any one time. The set procedure to command, without any <filename> specified, closes all active library files. A closed library file discards any knowledge of where the procedures within reside. The close procedure command provides the same functionality. The active procedures and functions can be listed with the list procedure command.

Dynamically Loadable Modules

Dynamically loadable modules provide Object Oriented encapsulation for LianjaScript/VFP code so that loading a library does not "pollute" the namespace and create potential problems due to name clashes.

Use the require() function to dynamically load a library and reference its public variables and procedures/functions in an OO manner: object.property, object.method().

The filename specified as the argument to the require() function must exist in the Lianja path or be prefixed with a special prefix e.g. lib:/ or thirdpartylibs:/

// mylibrary.prg
public myvar = 10
proc helloworld()
  ? "Hello World"
endproc
// end of mylibrary.prg
 
// myprog.prg
local mylib = require("mylibrary.prg")
? mylib
? mylib.myvar
mylib.helloworld()
// end of myprog.prg
 
myprog()
 
// Output
Dynarray (refcnt=2)
(
    [helloworld] => Procedure()
    [myvar] => 10
)
        10
Hello World

Working with Data

As the Lianja App Builder has an embedded database built into it, you do not need to install any other database software to be able to build multi-user high performance database apps for the desktop, web and mobile devices.

The Lianja embedded database engine is highly compatible with Visual FoxPro 9.0. It includes a feature-complete cross platform implementation of the Visual FoxPro scripting language and database engine.

If you know Visual FoxPro you can develop in LianjaScript leveraging all of your existing knowledge.

Additionally, Python, PHP and JavaScript are seamlessly integrated on top of the Lianja database engine, allowing you to build custom UI sections and custom UI gadgets in Visual FoxPro, JavaScript, PHP or Python and make full use of the power of this high performance database engine with local cursors and complete SQL and NoSQL database support.

To facilitate the development of custom UI sections and gadgets, Lianja comes with a cross-platform, UI independent application framework called the Lianja UI Framework.

Note that the Lianja database has a high degree of Visual FoxPro compatibility but has many extensions above and beyond Visual FoxPro to facilitate the deployment of high-availability systems with a high degree of fault tolerance.

If you are building a custom section in Visual FoxPro, whenever any of the methods of your section class are executed from your Lianja App, then the cursor for the table that is bound to the section will be active.

Accessing data in Lianja databases from Visual FoxPro is simple as this data-centric scripting language sits right on top of the Lianja database engine and everything works in a way that Visual FoxPro developers are already familiar with.

Note that if you are building a UI with the Lianja UI Framework, you can bind your UI controls to data sources in the Lianja database just by setting the controlsource property of the UI control to tablename.columnname, you do not need to write any special Visual FoxPro code to accomplish this.

Creating a database

To create a Lianja database from LianjaScript you use the CREATE DATABASE command.

create database mydb

Opening a database

To open a Lianja database from LianjaScript you use the OPEN DATABASE command.

open database southwind

Creating a table

To create a table in a Lianja database from LianjaScript you use the CREATE TABLE command.

open database mydb
create table mytable (firstname char(80), lastname char(80), age int)

Dropping a table

To drop (delete) a table from a Lianja database from LianjaScript you use the DROP TABLE command.

open database mydb
drop table mytable

Create a Cursor

You can then access a table in the database using the openRecordSet() method of the Database class using SQL or NoSQL. For example we can access the customers table using SQL like this:

select * from customers into cursor cust

Or alternatively just open the customers table with NoSQL like this:

use customers

Cursor data navigation

After we have opened a cursor we can navigate through the data using any of the cursor data navigation commands.

For example: to position on the first record in a cursor use the goto top command.

goto top

Extract data from the Cursor

When you are positioned on a particular record in a cursor you can extract data just by referencing the field name like this:

? customers.amount

Filtering selected data

When you open a cursor with an SQL select statement, the data selected is only that which matches the where condition of the select statement. If you open a table with NoSQL e.g.

use customers

You can filter the records that are returned using the data restriction commands.

For example to lookup a customer by their id and scan through the data selecting only those records that satisfy a certain condition you could write:

use customers order tag id
seek "12345"
scan rest while amount > 0
   ? "Amount is ", customers.amount
endscan

The SET RUSHMORE command enables or disables Rushmore-style (Visual FoxPro) index scanning optimization for NoSQL and SQL commands. SET RUSHMORE is ON by default. This reduces coding extensively.

use customers 
scan for id="12345" and amount > 0
   ? "Amount is ", customers.amount
endscan

NoSQL keyed data access

When opening a table in NoSQL mode, you can lookup records by keys.

use customers order tag id
seek "12345"
if not found()
   // key was not found.
endif

Adding new records

You can add new blank records to a recordset using the APPEND BLANK command.

use customers
append blank
replace id with "34567", amount with 0

Or alternatively the SQL INSERT command.

insert into customers (id, amount) values("34567", 0)

Multiple records can be inserted in one statement.

insert into southwind!example (account_no, last_name, first_name) values ;    
 ("20000", "Smith", "Sam"), ;    
 ("20001", "Jones", "Jack"), ;    
 ("20002", "Baker", "Beryl")

Note that after executing append blank the record is not written until the you move the record pointer as Lianja supports record buffering by default. This allows you to update the fields of the blank record prior to it being committed into the table.

Updating records

You can update records in a cursor using the REPLACE command.

use customers
set order to tag id
seek "12345"
if found() 
   replace amount with amount+1000
endif

Or alternatively using the SQL UPDATE command.

update customers set amount = amount+1000  where id = "12345"

Deleting records

You can delete records in a cursor using the DELETE command.

use customers
set order to tag id
seek "12345"
if found() 
   delete
endif

Or alternatively using the SQL DELETE command.

delete from customers  where id = "12345"

Closing a Cursor

You close a cursor using the USE command.

select customers
use

Closing a Database

You close a database using the CLOSE DATABASE command.

close database

Datasessions

If you are calling a function/proc that may interfere with the data session state (tables open, position in cursors etc), use the PUSH and POP DATASESSION commands to save and restore the state.

// myproc.prg
push datasession
open database someotherdb
use accounts
//...
pop datasession

Importing and Exporting Data

The Navigational Data Access commands append from and copy to can respectively be used to import and export Lianja data.

The SQL statements create table and insert allow data to be imported from XML files and the into, save as and to clauses supported by the SQL select statement offer a wide range of export formats for the selected rows.

Import From Text Files

The append from command imports data into the currently selected table from the specified file of the stated format. The file extension of the imported file is assumed to be .csv for type csv and .txt for all other types unless stated.

append from <filename> 
[while <condition as logical>] [for <condition as logical>]
[type] sdf | fixed | delimited | delimited with blank | delimited with <delimiter> | csv 
use example
// Standard data file
append from custsdf sdf
// Fixed length fields
append from custfix fixed
// Delimited (',' separated, "" around character fields)
append from custdel delimited
// Delimited with blank (single space separates fields)
append from custblank delimited with blank
// Delimited with <delimiter> (',' separated, <delimiter> around character fields)
append from custpipe delimited with |
// Microsoft Excel compatible comma-separated
append from custcsv csv

Import from XML

The SQL create table statement allows a new table to be created from metadata stored in an XML file and the data to be optionally loaded.

create table <table>
  [from] xml <.xml file> [load]
create table customer2;
  from xml cust.xml load

The SQL insert statement supports the import of data from an XML file:

insert into <table> from [xml] <xml filename>
insert into products;
  from xml prices.xml

Export to Text Files

The copy to command exports data from the currently selected table to the specified file in the stated format. The file extension of the exported file is assumed to be .csv for type csv and .txt for all other types unless included.

copy to <filename> 
[while <condition as logical>] [for <condition as logical>]
[type] sdf | fixed | delimited | delimited with blank | delimited with <delimiter> | csv 
use example
// Standard data file
copy to custsdf sdf
// Fixed length fields
copy to custfix fixed
// Delimited (',' separated, "" around character fields)
copy to custdel delimited
// Delimited with blank (single space separates fields)
copy to custblank delimited with blank
// Delimited with <delimiter> (',' separated, <delimiter> around character fields)
copy to custpipe delimited with |
// Microsoft Excel compatible comma-separated
copy to custcsv csv

The SQL select statement can export the selected data in text file format.

select <column-definition> from <table-definition> to file <filename>
select * from shippers to file shiptxt

Export to XML

The copy to command can export data from the currently selected table to an XML file. The file extension of the exported file is assumed to be .xml unless included.

copy to <filename> 
[while <condition as logical>] [for <condition as logical>]
[type] xml
use example
copy to custxml xml

The SQL select statement can export the selected data in XML file format.

select <column-definition> from <table-definition> into xml <xml filename>

or

select <column-definition> from <table-definition> save as xml <xml filename>
select employeeid, lastname, firstname;
  from employees into xml emp
select employeeid, lastname, firstname;
  from employees save as xml emp

Export to HTML

The SQL select statement can export the selected data in HTML file format.

select <column-definition> from <table-definition> into html <html filename>
select employeeid, lastname, firstname;
  from employees into html emp

Export to JSON

The SQL select statement can export the selected data in JSON file format.

select <column-definition> from <table-definition> into json <filename> [memobase64]
select employeeid, lastname, firstname;
  from employees into json emp

Integrating Python with LianjaScript

In LianjaScript you can execute any Python code using the built-in execPython() function.

# file: barrytest.py
import Lianja
import sys
 
def myfunc(theArg):
    Lianja.writeOutput("myfunc was called with " + theArg)
    return Lianja.evaluate("now()")
 
if len(sys.argv) > 0:
    global returnvalue
    returnvalue = myfunc( sys.argv[1] )

You can dynamically load this library (once) and call it in one statement.

result = execPython("barrytest::myfunc('hello world')")

Inside your Python code you can execute any LianjaScript code using Lianja.evaluate() and Lianja.execute().

now = Lianja.evaluate("now()")

The argument to Lianja.evaluate() can contain user defined functions and procedure calls. This provides full two way integration between LianjaScript and Python.

Lianja.evaluate("yourLianjaFunction('hello', 'world')")

This tight integration let's you use any of the Python AI, ML and other useful modules available and use them in LianjaScript.

Full details can found in the article Integrating Python with LianjaScript.

Building a UI

You normally build the UI of your App visually in the page builder. You can then specify delegate procs written in LianjaScript in the Page Builder Assistant or the App Inspector. These delegates respond to events such as a click or an interactivechange of text in a textbox or other UI control.

You can also develop custom UI code written in LianjaScript using the Ui Framework Classes. e.g.

local m_form = createObject("form")
m_form.addObject("m_cont", "container")
m_cont.backcolor = "lightgreen"
m_form.resize(600, 400)
m_form.show(1)