Learning Stanza: quick reference

JITX is implemented in the LB Stanza (Stanza) programming language. We like stanza because it supports types to help write correct code, and also allows higher-level programming. We can write Stanza programs inline with JITX code, to generate designs, components, and modules programmatically.

We do not need all of Stanza to develop with JITX; this document provides a short walkthrough of commonly-used Stanza features.

Comments

; this is a comment 
;<A>
This is a block comment 
    ;<B> 
        block comments can be nested with optional tags. 
    ;<B>
;<A>

Basics

defpackage learn-stanza-in-y: 
  import core 
  import collections 

;=========================================================================
; The basics, things you'd find in most programming languages
;=========================================================================

; Variables can be mutable (var) or immutable (val)
var mutable = "this one can be" 
val immutable = "this string can't be changed"

; The basic data types (annotations are optional) 
val an-int: Int = 12345
val a-long: Long = 12345L
val a-float: Float = 1.2345f
val a-double: Double = 3.14159
val a-string: String = "this is a string"
val a-multiline-string = \<tag>
    this is a multi-line string literal. 
<tag>


; print formatted strings with println() and "..." % [...]
println("this is a formatted string %_ %_" % [mutable, immutable])

; Stanza is optionally typed, and has a ? (any) type. 
var anything:? = 0
anything = 3.14159
anything = "a string"

; Stanza has basic collections such as Tuples, Arrays, Vectors, HashTables, etc. 
val tuple: Tuple<?> = [mutable, immutable]

val array = Array<?>(3)
array[0] = "string"
array[1] = 1
array[2] = 1.23455
; array[3] = "out-of-bounds" ; arrays are bounds-checked 

val vector = Vector<?>()
vector[0] = "string"
vector[1] = 1
vector[2] = 3.14159

val hash-table = HashTable<String, ?>()
hash-table["0"] = 0
hash-table["1"] = 1 
hash-table["2"] = 1 

Functions

; Functions are declared with the `defn` keyword 
defn my-function (arg:?) : ; note the space between identifier and arg list
  println("called my-function with %_" % [arg])

my-function("arg")  ; note the lack of a space to call the function

; If you try to define a function without a space, the syntax highlighter
; should give you a gentle nudge to fix it (ie, the red text and underline)

defn invalid-func(arg1, arg2) -> Int:
   ...

; functions can be declared inside another function and capture their environment. This is called a closure.
defn outer (arg): 
  defn inner (): 
    println("outer had arg: %_" % [arg])
  inner()

outer("something")

; functions are "first-class" in stanza, meaning you can assign variables 
; to functions and pass functions as arguments to other functions. 
val a-function = outer 
defn do-n-times (arg, func, n:Int): 
  for i in 0 to n do : 
    func(arg)
do-n-times("argument", a-function, 3)

; sometimes you want to define a function inline, or use an anonymous function. 
; for this you can use the syntax: 
;   fn (args): 
;       ... 
do-n-times("hello", fn (arg): println(arg), 2)

; there is a shorthand for writing anonymous functions, the above can be written
do-n-times("hello", { println(_) }, 2)

; the short hand works for multiple arguments as well. 
val multi-lambda = { println(_ + 2 * _) }
multi-lambda(1, 2)

; You can reference the arguments explicitly as well
;  with `_0`, `_1`, for the `Nth` argument.
val lambda-args = { println(_0, + (3 * _1)) }

User Defined Types

; Structs are declared with the `defstruct` keyword
defstruct MyStruct: 
  field:String

; constructors are derived automatically
val my-struct = MyStruct("field:value")

; fields are accessed using function-call syntax
println(field(my-struct))

; Stanza supports subtyping with a "multimethod" system based on method 
; overloading. 
; 
; This effectively defines an interface for type `Mytype` consisting
; of a single method `a-method`
deftype MyType
defmulti a-method (m:MyType)

defstruct Foo <: MyType:
  fab:Int

defmethod a-method (a-foo: Foo):
  println("called a-method on a Foo")

defstruct Bar <: MyType:
  bab:Double

defmethod a-method (a-foo: Bar):
  println("called a-method on a Bar")

The Type System

; True and False are types with a single value. 
val a-true: True = true 
val a-false: False = false 

; You can declare a union type, or a value that is one of a set of types 
val a-boolean: True|False = true 
val another-boolean: True|False = false 

; You can pattern match on types 
match(a-boolean):
  (t:True): println("is true")
  (f:False): println("is false")

; You can match against a single possible type
match(a-boolean:True):
  println("is still true")
else: 
  println("is not true")

; You can compose program logic around the type of a variable
if anything is Float :
  println("anything is a float")
else if anything is-not String : 
  println("anything is not an int")
else : 
  println("I don't know what anything is")

Control Flow

; stanza has the standard basic control flow 
val condition = [false, false]
if condition[0] : 
  ; do something 
  false 
else if condition[1] : 
  ; do another thing
  false 
else :
  ; whatever else
  false

; there is also a switch statement, which can be used to pattern match
; on values (as opposed to types)
switch(anything):
  "this": false 
  "that": false 
  "the-other-thing": false 
  else: false 

; for and while loops are supported
while condition[0]: 
  println("do stuff")

; More on For Loops shortly.
for i in 0 to 10 do:  
  vector[i] = i

; stanza also supports named labels which can functin as break or return 
; statements
defn another-fn (): 
  label<False> return:
    label<False> break:
      while true: 
        if condition[0] is False: 
            break(false) 
    return(false)

; Stanza has exceptions and provides a syntax similar to Python:

try:
  some-func()
  throw(Exception("Abort!"))
catch (e:Exception):
  println("Error: %~" % [e])
finally:
  println("We made it!")

; For a comprehensive guide on Stanza's advanced control flow, check out 
; this page: http://lbstanza.org/chapter9.html from Stanza-by-Example

For Loop Operators


; The for-loop has a special expression at the end called the "Operating Function"
; In most cases - it is simply the `do` operator: 

for i in 0 to 10 do:
  println("Index: %_" % [i])

; However the operating function can _do_ so much more!
; Operating functions typically have the form:

defn some-op (func, xs:Seqable) -> False:
  ...

; Here the first argument is a function, and the second argument
; is a sequence of objects

; Referring to our previous example:

for i in 0 to 10 do:
  println("Index: %_" % [i])

; When used in a for-loop, the body of the for-loop becomes the function
; `func`. 

defn body (i) -> False :
  println("Index: %_" % [i])

; The value `i` becomes the argument to `func`. The expression `0 to 10` becomes `xs`.

; There are many operating functions - `seq`, `map`, `filter`, etc. - defined in the stanza core library.
; https://lbstanza.org/reference.html#anchor478

Sequences


; for "loops" are sugar for a more powerful syntax. 
val xs = [1, 2, 3] 
val ys = ['a', 'b', 'c']
val zs = ["foo", "bar", "baz"]

for (x in xs, y in ys, z in zs) do : 
  println("x:%_, y:%_, z:%_" % [x, y, z])


;xs, ys, and zs are all "Seqable" meaing they are Seq types (sequences). 
; Sequences are called "iterators" in other languages, like Python, and 
; provide similar abstractions.
; 
; A common sequence task is concatenating sequences. This is accomplished 
; using the `seq-cat` operating function. This is analogous to 
; "flattening" iterators
val concat = to-tuple $ 
  for sequence in [xs, ys, zs] seq-cat:
    sequence

; we can also use a variation to interleave the elements of multiple sequences
val interleaved = to-tuple $ 
  for (x in xs, y in ys, z in zs) seq-cat : 
    [x, y, z]

println("[%,] [%,]" % [concat, interleaved])

; Another common task is mapping a sequence to another, for example multiplying
; all the elements of a list of numbers by a constant. To do this we use `seq`. 
var numbers = [1.0, 2.0, 3.0, 4.0] 
numbers = to-tuple $ 
  for n in numbers seq : 
    2.0 * n 
println("%," % [numbers])

if find({_ == 2.0}, numbers) is-not False : 
  println("found it!")

; or maybe we just want to know if there's something in a sequence 
var is-there = for n in numbers any? :
    n == 2.0 

; since this is "syntactic sugar" we can write it explicitly using an 
; anonymous function
is-there = any?({_ == 2.0}, numbers)

Documentation

;=========================================================================
; Documentation 
;=========================================================================
;
; Top level statements can be prefixed with the "doc" field which takes 
; a string value and is used to autogenerate documentation for the package. 
doc: \<doc>
    # Document Strings 

    ```stanza
    val you-can = "include code snippets, too" 
    ```

    To render documentation as markdown (compatible with mdbook)

    ```bash 
    stanza doc source.stanza -o docs 
    ```
<doc>
defn docfn () : false