## Language Reference Every program in SPADE is made up of a bunch modules, which are inturn made up of a bunch of statements, each of which tells the computer (the SPADE interpreter to be precise) to do one thing or the other. A bunch of statements can be wrapped in procedures/functions so that they can be reused throughout the code. Intermediate values can be stored in variables so that they can be used or referenced from various places. ### Modules A module is just a file that contain SPADE code. You can import a module using the `import` function. The snippet below imports a module and calls a function inside it. ``` let common = import("/path/to/the/module.spd") common.a_function_in_the_module() ``` You can read more about modules and imports in #link: Modules and Imports# section. ### Variables Variables are initialized using `let` statement. For example ``` let myVar = 10 ``` creates a variable named `myVar` with a value of `10`. The same statement can also change the value of an existing variable. ### Procedures Procedures are a way to reuse code by bunching statements together and providing a way to call or invoke them from different parts of the program. In SPADE, They are declared using the `proc` keyword. Procedures can have "arguments", which are just values that are passed to and made available in the scope where the statements in the procedure are executed. You can customize the behavior of the procedure using the arguments. For example, if you have a procedure to draw a circle, you can use arguments to pass the center and radius of the circle that should be drawn. Procedures can also "return" a value to the calling code, so you can optionally pass the result that was computed by the statements in the procedure. For example Here is a procedure to multiply two of its arguments and return the result. ``` proc multiply(a, b) return a * b endproc let product = multiply(5, 2) -- Here we call the "multiply" procedure println(product) ``` ### Anonymous procedures An anonymous procedures are a single use, throwaway procedures without a name. They are useful as a callback to pass some logic into another procedures. You can create anonymous procedures using the `fn` keyword. Anonymous procedures can contain only a single expression, and the value of the expression is returned automatically. Below is an Example where a callback using an anonymous procedures is passed to the `filter` function. So here we have a list of numbers, and using the `filter` function we remove the elements that are less than three from it. An anonymous function is used to pass the logic that checks if the number is less than three. ``` let a = [1, 2, 3, 4, 5] let b = filter(a, fn (x) x >= 3 endfn) -- anonymous procedures that returns true for numbers >= 3 inspect(b) -- will print [3, 4, 5] ``` Named procedures can also be used in place of callbacks. So if you want to include more logic inside a callback, you can use a regular named procedure as a callback. For example the above program can be written as, ``` proc filterFn(x) return (x >= 3) endproc let a = [1, 2, 3, 4, 5] let b = filter(a, filterFn) -- The procedures name `filterFn` is used in place of a callback. inspect(b) ``` There is no requirement that anonymous procedures must return a value. But if the code that uses it expect a value to be returned from it, and it does not return a value, then it will be a run time error. #### Global variables A global variable is just a variable who's definition is not inside a procedure. Such a variable is available everywhere inside the module it is defined in. See the example below. ``` let myGlobal = 10 proc printMyGlobal() println(myGlobal) -- Variable "myGlobal" is available here endproc printMyGlobal() ``` ##### Updating a global variable Updating global variables from within a procedures require special syntax. You should use the `global` keyword for this. Not using the `global` keyword just creates a local variable with the same name, within the scope of the procedure. For example, if you do the following, ``` let myGlobal = 10 proc printMyGlobal() println(myGlobal) endproc proc updateMyGlobal() let myGlobal = 20 endproc printMyGlobal() updateMyGlobal() printMyGlobal() ``` In the above program, we first print the value of global variable `myGlobal` using `printMyGlobal` procedure and then we call another procedure that tries to update the value of this global variable, and then we again print the global variable using the `printMyGlobal` procedure. We see that in the last step, the `printMyGlobal` procedure still prints `10`. This is because the following line, ``` let myGlobal = 20 ``` instead of updating the global variable, created a local variable with name `myGlobal`, and the global variable was untouched. To update the global variable, we should change this line to, ``` let global myGlobal = 20 ``` When using this syntax, the global variable that is referenced should exist already. If there is no global variable by the name referenced, then it would be a runtime error. In other words, you cannot create new global variables using this syntax. ### Conditionals #### if-then This is used to conditionally execute parts of a program. For example ``` if age > 10 then println("Greater than 10") endif ``` #### if-then-else This is used to provide an alternative if the condition does not hold. ``` if age > 10 then println("Greater than 10") else println("Less than or equal to 10") endif ``` #### if-then-elseif-else This is like if-else statemebt but can be used to provide many alternative branches. ``` if age > 10 then println("Greater than 10") elseif age > 5 then println("Greater than 5") elseif age > 2 then println("Greater than 2") else println("Less than or equal to 2") endif ``` ### Lists Lists can hold an arbitrary number of values. For example, ``` let myList = ["one",2,3] ``` Lists in SPADE are one based so list indexes starts from 1 and not 0. So in the above list, the index `1` will have value "one", the index `1` will have value `2` and index `3` will have value `3`. You can read more about list indexing at #link: Index access# ### Dictionaries Like lists, dictionaries can also hold muiltiple values, but in a dictionary each value can be associated with a key. In the following snippet you can see how a dictionary is initialized and how indvidual values can be accessed. ``` let person = { name: "John", age: 25 } println(person.name) -- prints "John" println(person.age) -- prints 25 ``` You can read more about key access at #link: Key access#. ### Loops Loops are using to execute a sequence of statements repeatedly. Following looping constructs are available. #### for statement This loop can be used to execute some group of statements repeatedly, while incrementing a variable at each iteration. Example: ``` for i = 1 to 100 println(i) endfor ``` A step value can be optionally provided. The following example will print 1, 3, 5, 7 and 9 and so on till the end value is reached. ``` for i = 1 to 10 step 2 println(i) endfor ``` The start/end/step value can be either interger or fractional. The break statement can be used to break out of the loop. #### while statement This loop execute a group of statements repeatedly as long as the specified condition hold. Example: ``` let i = 10 while i < 0 println(i) let i = i + 1 endwhile ``` The `break` statement can be used to break out of the loop. ### loop statement This is an unconditional looping statement. The only way to exit from the loop is by using a `break` statement. Example: ``` let l = 0 loop println(l) let l = l + 1 if l > 10 then break endif endloop getkey() ``` ### Scoping Variables defined inside the procedure are local to the procedure and is available everywhere inside the procedure. Variables assigned at the top level are globals and are available as readonly values inside procedure without special syntax. For example, the program below will print `10` when executed. ``` let a = 10 proc myProc() println(a) endproc myProc() ``` There is a caveat to this though. Global variables defined below a procedure call will not be available inside the procedure. For example the following program results in an "Unknown symbol reference" error. ``` proc myProc() println(a) endproc myProc() let a = 10 -- ^ Global variable `a` defined after `myProc` call. Variable `a` -- is not available inside `myProc`. ``` Any assignment to a global variables inside a procedure will just create a local variable with the same name. For example, the following program will print `10`. ``` let a = 10 proc myFn() let a = (a + 1) endproc myFn() println(a) ``` If you want global mutable values, you can use a global mutable reference, see #link: newref#. #### Closures When a procedure returns an unnamed procedures, the returned procedures holds a copy of the parent procedures local scope. For example, the following procedure prints `10 20` ``` proc myFunc(x) return fn() print(x, " ") endfn endproc let fn1 = myFunc(10) let fn2 = myFunc(20) fn1() fn2() ``` ### Expressions Expressions are things that can be evaluated to a value. Following types of expressions are available in the language. #### Literals The language support the following literals. Integers -> `1500` Boolean -> `true`, `false` Floating -> `12.34` Bytes -> `0x21` String -> `"SPADE"` List -> `[12, 34, 56]` Dictionary -> `{name : "John", age: 34}` Anonymous function -> `fn(x) x * 2 endfn` #### Variable expression These refer to a variable, and evaluate to the same value that is held by the variable. #### Conditional expression Expressions that evaluate to one value or other based on a condition. ``` let p = if x == 0 then 5 else 10 ``` #### Binary expression The following binary operators are available. ##### Numeric operators `+` : addition operator `-` : substraction operator `*` : multiplication operator `/` : division operator ##### Boolean operators `<` : less than `>` : greater then `<=` : less then or equal `>=` : greater than or equal `==` : equality check `!==` : non-equality check `and` : boolean AND `or` : boolean OR The boolean 'not' operation is provided as a function #link: not#. #### Function calls Evaluates to the value returned by the function, for example, ``` proc multiply(a, b) return a * b endproc let a = multiply(5,2) ``` #### Index access An index in a list/string/bytes expression can be accessed using the index accessor. For example, ``` let list = [1,2,3,4] print(list[2]) -- prints 2. ``` NOTE: Indexes starts from 1. Index accessor can be attached to any expression, for example a function returning a list. For example, ``` proc myFunc() return [1,2,3] endproc print(myFunc()[2]) -- prints 2. ``` You can modify values using indexes too. ``` let list = [1,2,3,4] let list[2] = 10 print(list[2]) -- prints 10 ``` This works similarly for bytes and texts. ``` let b = 0xffff let b[2] = 0 print(b) -- prints 0xff00 ``` and for strings ``` let b = "abcd" let b[2] = "e" print(b) -- prints "aecd" ``` #### Key access An key in a dictionary expression can be accessed using the key accessor. For example, ``` let person = { name: "John", age : 39 } print(person.name) -- prints "John" print(person["age"]) -- prints 39 let k = "age" print(person[k]) -- prints 39 ``` Key accessor can be attached to any expression, for example a function returning a dictionary. For example, ``` proc myFunc() return { name: "John", age : 39 } endproc print(myFunc().name) -- prints "John" ``` ### Builtin data types #### Numbers Numbers are either represented as integers (which can be as big as required) or double precision floating point values. Mathematical operations convert from integer to floating point values as required. ``` let age = 45 let price = 0.23 ``` #### Strings Strings represent a sequence of unicode code points. ``` let name = "SPADE" ``` #### Bytes Bytes represent some sequence of bytes. ``` let somebytes = 0xFFAB ``` #### Lists Lists can hold a sequence of values. ``` let mylist = [ "SPADE", 10] ``` Item at an index `x` in list `l` can be accessed using notation `l[x]`. Indexes start at `1`, so to access the first item in a list use `l[1]`. See #link: List Functions# #### Dictionary Dictionaries are containers that can hold content at a string key. For example, the data for a person can be held in a dictionary. ``` let person = { name: "John", age: 45} ``` indvidual items can be accessed using the key access notation. For example to print the name of the person, we can do something like the following. ``` println(person.name) ``` #### Modules and Imports You can split a program into a number of source files or source modules. You can import a module using the #link: import# function. The #link: import# function returns a module value, and the functions inside the imported module can be accessed using the dot operator. For example, ``` let m = import("mymodule.spd") println(m.myDouble(2)) ``` Where contents of "mymodule.spd" is ``` proc myDouble(x) return 2 * x endproc ``` A module is just a value, so it can be passed to functions, stored in a variable etc. Modules have their own scope and importing a module executes the top level statements in it. So Modules can also import each other recursively as long as the top level statements, if any, does not trigger a recursive execution. Only function from a module can be accessed from other modules. If you want to export a value from a module, you should wrap that in a function and return it from there. When a module is imported, top level statements from that source file are executed. A module gets its on private scope, and the global variables in a module reside in this private scope. This scope is retained and restored between calls to functions in this instance of the module. The module scope is not shared between instances of the same module. To update a module global variable use the `let` statement with `global` keyword. So `let global x = 10` stores the value `10` in module global variable `x`. If the global variable does not exist at this point, it is created.