spade-0.1.0.9: docs/language-reference.md
## 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.