Open All Sections     Close All Sections

Lima (pronounced like "lima bean") is a high-level gradually-typed platform-independent object-oriented language made to be easy (and terse) to write. It allows seamless language extension via fully-arbitrary macros, and has the ability to specify expected input behavior and intended performance behavior of your program in addition to intended output behavior. Optimizer modules give the programmer much more control over how their program is automatically optimized.

Lima has a number of uncommon features like transparent pointers (references), rich operator overloading, and concurrent semantics, along with less uncommon features like dynamic dispatch.

The implementation can be found on github at https://github.com/fresheneesz/lima.

Here's an example of an HTTP server equivalent to this node.js HTTP server at the top of the page.

use['net'] port = 3000 try: listener = net.http.listen[port] when listener.state == 'listening': logger.i['Server is listening on port 'port] df listener.in request: request.respond[200 'Hello Lima Server!'] catch err: logger.e['Something bad happened.' err]

Without further ado, some code examples:

target[x86.win32 ppc.mac jvm] ; Cross compiles - the 'target' declares target platforms and formats to compile to var websocket = load['websocket'] ; external module loading string lastWords = 'Goodbye World.' ; module variable (not a global variable) y = 5 ; implicit variable declarations doSomething[] ; a function call - arbitrary code can be called in a module definition ; The function called above - note that it is hoisted meaning that it is defined at the top of the scope ; no matter where it is in that scope. hoist doSomething = fn: var myVariable ; Variable declaration myVariable = 'Hello?' ; Assignment mut int[x=8 y] ; Declaration and initialization of a mutable variable. z = 5 ; Throws exception - no implicit variable declarations in functions GunIt[lastWords] ; Function call if y < x: ; Conditions x += y else: x -= y var numbers = {1 2 3 4 5} ; Lists/arrays var cubedList = numbers.map[v^3] ; List comprehensions var evens = numbers[[v%2==0]] ; List slices (here getting a list of the even values) var square = fn v: ; First class nested functions ret v^2 ; Exponentiation operator var cat = ; Objects { legs = square[2] ; Implicit member declarations sound = 'meooow' 'siblings': {'Mr TFeathres' 'Furkrisake' 'Pawscat'} {'a' 'b' 3}: 'paws' ; Objects/lists can be keys makeNoise = fn: ; Object methods logger.i[sound] } var race = fn winner otherRunners: ; Splats (here, being used for varargs) logger.i['The winner is: 'winner@] ; @ stands for a newline - like \n in C logger.i['Runners up: '@] df otherRunners runner: ; for-each style loops logger.i[runner@] ; Destructuring assignments (x gets cat.legs - the value 4, w gets cat.bam[1] - the value 2) var w {legs:x bam:{1:w}} = cat

Lima has a few defining traits:

And also a couple interesting features:

This section gives a brief high-level overview of what's available in Lima and how Lima "looks". This section is non-normative. The sections "Variables" through "Standard Library" are normative.

Values and Operators

In Lima, all values are objects, including nil. You can interact with an object only via its operators. The operators defined for an object determine how that object behaves. The major types of values are:

All those types of values other than macros have a literal syntax for defining them (see the section on "Literals").

Variables and Types

Variables in Lima must be declared with a type. However, Lima offers two general types: var which matches any value other than nil, and var? which matches any value at all. Like in rust, variables in Lima are immutable by default, and must be declared mut if you want them to be mutable. Any variable assigned in an object literal is treated as a variable declaration with the type var. Since modules are themselves objects, this includes variables assigned at the top-level in a module.

a = 42 f = fn: var b = 'hi' mut int patients = 99 mut obj = { strength = 34 mut health = 99 fn die: health = 0 patients-- }

Types are first-class values in Lima and so can be stored in variables and passed or returned from functions.

Pair = fn type T: return {T[a] T[b]}! Pair[int] x = {a=12 b=34}

Flow Control

Flow control in lima is relatively similar to other major languages. You have an if construct:

if something == 5: ; Do something

a for-each style loop construct:

df aList oneValue: ; Do something with each value

a while-style loop construct:

mut int x = 0 while x < 5: ; do something until x is greater than 4

exception throwing and catching:

try: ; Do stuff throw "Oops, a problem" e: ; Catch and handle exception

first-class functions:

process = fn operate a b: ret operate[a b] add = fn a b: ret a+b logger.i["4 + 5 ="process[add 4 5]] ; Prints "4 + 5 = 9"

There are also some more interesting flow control options, like lightweight thread syntax and event handling:

var done = false var file = thread: ret downloadLargeFile['someFileName.wtvz' 'utf-8'] ; runs this asynchronously file.wait: logger.i["It's finally done!"]

The loop constructs have scoping rules like in C - variables created inside a df statement, for example, will only exist within the loop block. However, many (like if and try) do not define new scopes and thus variables defined within them can be used outside of them. In fact, the only core constructs that create new scopes are df, while, thread, fn, and macro. All the flow-control constructs are detailed in the sections on "Core Macros" and "Standard Macros" (tho not all things there are necessarily flow-control).

Modules

In Lima, modules are objects like everything else. The top-level of a Lima source file is the same as the inside of an object literal. Loading a module simply loads that module as an object.
use['color'] use['./renderer'] foregroundColor = color.lightgray backgroundColor = color.black renderText = fn container text: mut textToRender = text if text.color - container.color < 40: textToRender = text.color.contrast[container.color 40] renderer.render[container textToRender]

In a given lima source file, which from now on I'll call a , you can define and initialize variables in a (similar to the top level in a C or java source file). Variable declarations include function definitions and object initializations. You can declare a variable anywhere you could use a variable, including in functions (e.g. setToFour[int[newInt]]).

Variables not in a must always be defined with a type. Variables that are defined in the , have the implied type const var if none is specified (which is a constant variable with a type that includes all values except nil - similar to javascript's "var" and Java's "Object" type). To make a variable mutable, you need to define it with the macro mut. Statements in the are executed in the order they're defined, except that all hoisted statements are executed before all non-hoisted statements (see the section on hoist under Core Macros).

Variable names can contain the characters a through z, A through Z, 0 through 9, and _ (underscore). They can't start with a number. Also, _ is a reserved variable name that can't be declared (tho "_" can be used as an object property key).

Inside the , every line must be a variable/member definition (or if statement).

Note that an uninitialized variables may not be used until they've been set. Variables declared in a module (in the of a module) exist only in the module's scope, like in javascript and java. This is explained further in the section on objects.

var variable ; This is a variable declaration. mut anotherVariable = 5 ; Mutable variable declaration initialized to 5. anotherVariable = 8 ; This would throw an error because it is an implicit variable declaration of a variables that is already declared.

Variables declared in a function must be case-insensitive-unique in its scope, which means that no function variable can have the same case-insensitive name as another variable in the same scope (ie not in the same function nor in any upper scope). Similarly, private members declared in an object also must be case-insensitive-unique in its scope. The only exception to this is that two different variables can differ in the case of their first character only (eg. DuckAssassin and duckAssassin can coexist, but duckassassin can't coexist with either of them). Public members, however, can shadow names in a higher scope.

mut ay=5 var ay=6 ; errors because ay can't be declared twice in the same scope var aY=7 ; errors because aY is the same case-insensitive variable name as ay var Ay=8 ; is ok because the first character's case is different than ay var x = { private var ay ; Errors because private variables can't implicitly shadow variables in an upper scope. var ay ; is legal, but this y must be accessed with , and the original ay cannot be accessed at all within this object var x ; is legal, but has the same restrictions as ay (since there is an x in the upper scope) var[a=b=c=90] ; three variables declared and set to 90 var[d=1 ; two variables declared over multiple lines e=2 ] var: f g ; two variables using the var type's colon syntax var: h=1 ; three variables using colon syntax on separate lines (with indendation) i=45 j='something else' var z = fn: var x ; errors because x is already declared (actually in two places: the name of the object holding this function and there is a member with that name as well) fn! f = fn int[x y]: ; errors because both x has already been declared above ret x+y }

Most of the time, operating systems only have one entry point. For OSs that have multiple entry-points (like Android), when an entry-point is started, the entry-point module will be built and then the public method with the appropriate name for the required entry-point will be called. In most systems with only one entry point, no specifically named function need be defined.

Note that the arguments given to the program are contained in the process object.

As you've probably already seen, comments in lima begin with a single semi-colon: ;. This means that programmers used to using semi-colons to end their statements won't (usually) get tripped up by doing it, they'll just be adding an empty comment every line.
; Single-line comment (everything after it on the same line is a comment).
;[ ;] Span-comment. Can be used as a multi-line comment, or a comment within a line. Span-comments can nest, meaning that the ;[ and ;] follow normal bracket-matching rules. This means that you can comment out a small part of code, and then comment out a larger part of code that includes the smaller commented out part - without rearranging the comment markers.
var x ;[ a variable ;] = 4 ;[ var y var z ;[ used somewhere else ;] ;]
All comments are treated exactly like whitespace. So, for example, abc;[;]def is two tokens, abc and def, not one abcdef token.

In order to accommodate macros in a sane way, lima statements are structured in basic blocks where any subsequent lines in a multi-line statement must be indented further than the first line is indented OR start with an ending paren, bracket, or brace.

; `b` must be indented at least one space further than the beginning of the statement. a + b ; Again, b must be indented at least one space further than the first line of the statement (which ; begins at `someFunction`). someFunction[a + b] ; In this case, the macro might only consume part of ` (potential macro input)` but will never consume any ; of `(more source code)` since its not indented within the statement. a + macro (potential macro input) (more source code) ; As long as subsequent lines in a statement are indented, the indentation doesn't matter. a + b + c ; End-brackets, end-parens, or end-braces can have the same indent while continuing the statement. 2 + someFunction[ a ] + anotherFunction[ b ] a + (b + 2 ) + ( 2 * c ) 1 + {1 2 3 4 5 6 }[2] ; `if` statement conditions must also be indented, including `else`. if x: doSomething[] else: doSomethingElse[] ; However, all the following are errors: a + b ; This line is not part of the previous statement. someFunction[ a ; This line is not part of the function call. ] a * ( b+c ; This line is not part of the previous statement. ) if x: doSomething[] else: ; This line is not part of the if statement. doSomethingElse[]

The indentation of a statement depends on what column that statement starts on (as opposed to the indent of the first statement on that statement's line). So for lines with multiple separate statements, statements other than the first statement have a different indentation level than the first statement.

a+b c+ d ; this is ok and is part of the logical statment `c+d` ; This is an error. a+b c+ d ; And this is also an error, because `d` isn't indented *further* than the start of the statement `c+` makes up. a+b c+ d

Whitespace delimitation is more forgiving then their python counterparts. You're free to indent separate statements without regard to how other statements within a given construct are indented. You can certainly produce ugly code this way, but indentation is one technique that can help visually organize sections of code that have many statements with interwoven relations.

; These 3 statements simply execute one after the other. a=3 b=4 c=5 ; This is a list with 3 elements. var x = { z+y x+w g+h }

Note that tabs aren't allowed to be used in Lima source files at all. Any tabs must be converted to spaces or space-extension characters. The compiler and IDEs should help do this.

These rules are the basic ways that limit how statements are chopped up, but there are other conventions used by core and standard macros (like if or fn) that are detailed in the Conventions section below.

Operators are the basic form of interaction with values. No multi-character operator can end in a single . and no operator can end in a single : (ie : is not an operator). Other than that, an operator can be made of any combination of the symbols:

An operators consisting of the above symbols must have whitespace between them to be treated as separate operators. Note that if an operator has no whitespace separating it from a . or :, this is not an error but they are treated as separate from the operator they follow. These are also operators:

Binary Operators

Binary operators are the most familiar type of operator. Uses infix notation. A binary operator may not written directly next to it's left or right value unless its written directly between both.

This is primarily so that there is a clear syntactic difference between binary and unary operators, so that the compiler can tell where a statement ends and the next one begins.
2*5 ; Fine. 2 * 5 ; Fine. 2 * ; .. 5 ; Fine. ; These lines actually represent 2 statments each (ie each line is parsed as two expressions). 2 *5 ; Throws exception (since numbers don't have a * prefix operator). 2* 5 ; Throws exception (since numbers don't have a * postfix operator).

Unary Operators

Unary operators can either be pre-operators or post-operators - either preceding or following the value they operate on, respectively.

Unary operators must have no spaces or newlines between the value they are operating on. Operators between two values without any white separation are treated as binary operators, not unary.

a! b ; postfix exclamation operator a !b ; prefix exclamation operator a!b ; binary 3 ! ; exception since this isn't part of the statement on the preceding line 4 var x = 3 ! ; exception - its too far away var y = ! a ; same exception var z = {1 -5 9} ; treated as {1 (-5) 9} var w = {1-5 9} ; treated as {(1-5) 9} var h = a-b ; binary minus var h2 = a - b ; also binary minus var h3 = a - b ; also binary minus var h3 = a -b ; unary minus

Destructuring Assignment

A special case of assignment, a desctructuring assignment looks like an object-literal lvalue (on the left side) assigned to a value that has a similar structure to the object-literal lvalue. The keys in the lvalue indicate what part of the structure of the rvalue to grab. If the entity at a key is a variable-name, that variable will be assigned to the value at the corresponding key in the rvalue. If the entity at a key is another object-literal, then the process is repeated with inner-object members (of both the lvalue and corresponding members of the rvalue).

mut[a b c] {a b c} = {1 2 3} ; same as a=1 b=2 c=3 {x:a y:b z:c} = {x:4 y:5 z:6} ; same as a=4 b=5 c=6 {j=a k=b m=c} = {j=7 k=8 m=9} ; same as a=7 b=8 c=9 {{a b} c} = {'hi' 'there'} ; same as a='h' b='i' c='there' ; not all rvalues need to be consumed {x:{y:{z:a}} = {w:4 x:{y:{z:3}}} ; same as a=3 {a b c} += {1 2 3} ; same as {a b c} = {a b c}+{1 2 3} which is the same as a+=1 b+=2 c+=3 {var[r1 r2}} ~> {'a' 'b'} ; points r1 at 'a' and r2 at 'b'

Note that all the values in the rvalue are evaluated before executing the assignments, and therefore it can be used for inline swapping, even in cases where the values being swapped aren't simple values, like accessors.

mut[a='hi' b='lo'] {a b} = {b a} logger.i[a b] ; prints 'lohi'

Which has the same affect as:
mut[a='hi' b='lo'] var temp = a a = b b = temp

Destructuring assignments work with =, any compound assignment operator, and the reference pointing operators (~> and ~>> etc), but work with no other operator. This feature is taken from Coffeescript and is a generalization of sequence unpacking in Python.

These are the order of operations for all binary operators defined by Lima's core language. Dereference Operators, Parens, and Unary operators do not have arbitrary order of operations like binary operators. They always have their listed order of 0 (for dereference and parens) or 1 (for unary operators).
(everything is evaluated from left to right except assignment operators and the ^ operator)
Order 0 Evaluated First Dereference Operators and parens () . ~ [] [[]] [? ] [[? ]]
1 . Unary operators ! - ? ...
2 . Top-level (arithmetic) ^
3 . Mid-level (arithmetic) * / // % mod cross
4 . Bottom-level (arithmetic) + -
5 . Range Operators ...
6 . Comparison == <> ?? != !<> !?? > < >= <= >>   <<
7 . Upper-level (boolean) & !$ !&
8 . Lower-level (boolean) and the nil-coalescense operator $ ||
9 Evaluated Last Assignment (evaluated from right-to-left) and the pipe operator (evaluated from left-to-right) = -= += *= ^= /= %= ~> ~>> //= : :: |
In a given precedence level, left-to-right associative operators are performed first before right-to-left associative operators, which means you can think about right-to-left associative operators as having a half-level higher precedence order (eg for level 5, right-to-left could be thought of as at level 5.5). The resolution order of an expression works like this: Note "resolve it" means to resolve the operator on its operands and return to step 1 with the newly simplified expression. Also, half-levels will refer to right-to-left associative operators at the rounded-down level (so the last level is 9.5)
  1. Start at precedence level 0 on the first operand
  2. If there is a paren operator for the current operand, resolve it
  3. If there is a ~ operator for the current operand, resolve it
  4. If the current operand is a macro, resolve it
  5. If there is a dereference operator at the current precedence for the current operand, resolve it
  6. If there is a prefix operator at the current precedence for the current operand, resolve it
  7. If there is a postfix operator at the current precedence for the current operand, resolve it
  8. If the current and next operands are only separated by a binary operator at the current precedence and associativity
    1. If the second operand doesn't have a postfix operator or higher precedence bracket or binary operator after it, resolve the binary operation
    2. If it does (have a postfix operator or higher precedence bracket or binary operator after it), return to step 2 at precedence level 0 on the next operand
  9. If the expression has resolved to a value, its done
  10. Otherwise return to step 2 at the current level plus .5 (ie move to left-associative operators for that level, or the next level if left-associative operators have already been evaluted for that level).
Example:
var a={x:1} b={x:2} -a.x + -b.x*4 + 5*6 ; will resolve to.. -1 + -b.x*4 + 5*6 ; will resolve to.. -1 + -2*4 + 5*6 ; will resolve to.. (-1) + (-2)*4 + 5*6 ; will resolve to.. (-1) + (-8) + 5*6 ; will resolve to.. (-9) + 5*6 ; will resolve to.. (-9) + 30 ; will resolve to.. 21

The following operators are here to give a general overview of the operators defined in the core language, but the operators are defined in full detail by the objects that they're members of.

Binary Operators

= Changes the value of a variable.
~> ~>> Sets a variable as a reference to another variable.
|| The . Returns a if a is not nil, and b otherwise. Similar to the "Elvis operator"

. Member access.
+ Normal addition, matrix/vector addition..
- Normal subtraction, matrix/vector subtraction.
* Multiplication, matrix product.
/ Division, product of matrix inverse.
// Discrete division.
% Modulus, extended-space character.
^ Exponentiation.
! Factorial.

& Logical and, set-conjunction.
$ Logical or, set-union.
!$ Logical xor, set exclusive-disjunction.

== Equality.
!= Opposite of ==.
<> Set equality.
!<> Opposite of <>
< Less than, proper subset, naturally ordered preceding string.
> Greater than, proper superset, naturally ordered proceding string.
<= Less than, improper subset, naturally ordered preceding string or equal string.
>= Greater than, improper superset, naturally ordered proceding string or equal string.
<< >> <<= >>= Key-aware comparisons.

.. Range operator for numbers and strings. For example 2..10 returns a list with elements 2 through 10.
?? Tests if two values are the same data (e.g. two references that point to the same value).
!?? Opposite of ??

, Concatenation of strings

Unary Prefix Operators

- Negative.
@ String newline.

Unary Postfix Operators

...

Splat operator (aka the spread operator in Javascript).

?

Used as the safe traversal operator that allows safe traversal of members and properties without having to check for existence or nil. Also is used to create types that can hold nil. Both are postfix operators.

~

Accesses an object representing a reference (rather than the value it points to).

!

Interface operator - creates a type defined by the object's interface. Also used for creating special string characters.

++

Increment.

--

Decrement.

@ String newline.
operator[ order order backward chains ] = function

When used as an lvalue, operator is used for binary operator definition, redefinition, or overloading. Defines the based on the function.

Humans process symbols and pictures far faster than words. Programs that read like an essay can be far harder to read and understand, no matter how language-like they may sound. This is one reason that nobody likes word-problems in school. Operator overloading helps us visualize what a statement does far better than a series of function calls could.

For binary operators defined with one parameter, the operator is defined the same way when the object is on either side of the operator (note that this doesn't necessarily mean the operator is commutative). The parameter represents one operand and the other operand is assumed to be this.

a = { var x = 2 operator[ + ] = fn other: ret x+other } var c = a+5 ; c gets set to 7

For binary operators defined with two parameters, the first parameter represents the left operand and the second parameter represents the right operand. The parameter list must contain this as at least one paramter, which indicates that the current object is in that operand position.

order defines what operator precedence it has - the operator has the same precedence as the other operators that have the same order number. New orders can't be created.

The keyword backward indicates that the operator is right-to-left associative (like =). Operators are left-to-right associative without that keyword.

The type varWord can be used to capture variable names (both declared and undeclared) in an operator's dispatch. This allows you to do things like specify how the dot operator can operate on the name given and rather than the value that variable holds in scope. The left operand cannot be typed with varWord (see justifications.txt).

a = { operator[+] = fn this varWord word: var name = meta[word].name this[name] = true } var x = 5 a+x a['x'] ; holds true now

Just like for methods, when an operator is redefined on an object, only that definition is used - it doesn't retain inherited operator definitions, even if those definitions could handle arguments that the new definition can't handle. Also, operator definitions are , so once its defined it can't be redefined unless the override macro is used.

Operator assignments are hoisted (see hoist in the Core Macros section).

Dispatch ambiguity and disambiguation

If both operands in a binary operation have relevant dispatches for the operator, an exception will be thrown because the language can't tell which operand to use the operator of.

type hasV = {var v}! var a = { var v = 2 operator[+] = fn hasV! other: ret other.v + v } var b = { var v = 3 operator[+] = fn hasV! other: ret other.v - v } var c = a+b ; throws an error because the + operator of both operands match
However, if both operands have a matching operator dispatch, if it can be determined that one dispatch list is strictly more specific than (eg a strict subtype of) than the other, that dispatch will be chosen.
type hasV = {var v}! var a = { var v = 2 operator[+] = fn hasV! other: ret other.v + v } var b = { var v = 3 operator[+] = fn a! other: ; note the a! here rather than hasV! ret other.v - v } var c = a+b ; c gets 1 (rather than 5) because b's operator has a more specific interface
But if there is no more-specific dispatch, you can define the function resolveOperatorConflict to decide which operand's operator to use in a conflict.
var resolveOperatorConflict = fn a! x b! y operator: ret x var c = a+b ; c gets 5 (rather than 1) because resolveOperatorConflict chooses a's operator
Its also easy to inherit resolution functions from other modules like this:
var someModule = load['someModule'] var resolveOperatorConflict = fn a! x b! y operator: ret x other...: ret someModule.resolveOpConflict[other...]
Furthermore, if the operators for both operands uses var or var? typed parameters, the operator defined by the lvalue will be used. This is called 'weak dispatch' in Lima.
var a = { operator[+] = fn other: ret 'a' } var b = { operator[+] = fn other: ret 'b' } var c = a+b ; c gets 'a', since 'a' is the lvalue here
Lastly, in order to change this behavior, you can define a function called resolveWeakOperatorConflict (that has the same form as resolveOperatorConflict), which you can use to decide to use the rvalue in cases like this if you need to.
var resolveWeakOperatorConflict = fn x y operator: ret y var c = a+b ; c gets 'b' (rather than 'a') because resolveOperatorConflict choose b's operator

Restrictions

The following operators can't be overloaded:

  • :
  • ::
  • ??
  • |
  • .=
  • [= ] (and any variation of [[= ]] or [[[= ]]] etc
  • ~ (and all variations of ~~, ~~~, etc)
  • ~> (and all variations of ~~>, ~~~>, etc)
  • ~>> (and all variations of ~~>>, ~~~>>, etc)
The following operators can be overloaded but must be binary and have special handling:
  • =
  • .
The following operator can be overloaded but must be a postfix operator and have special handling:
  • ...
Also note that only the bracket operators and the dot operator can return macros. This restriction might be lifted in the future.

Creating chaining operators

The keyword chains indicates that the operator will take an extra parameter on the end (two or three parameters total, depending on what form you choose). The extra parameter is either nil if the operation is the first in the chain, or holds an object with the properties:

  • prevOp - A string containing the previous operator in the chain.
  • chainedValue - The value sent from the previous operation in the chain.
An operator will only chain to operators in the same order as it.

The operator should return a list where the first element is the normal return value, and the second element is the value to pass to the next chained operator (if there is one).

var Comparable = { var value make val: value = val operator[< chained] = fn this Comparable! x chainInfo: ret lessThan[this.value x.value chainInfo] Comparable! x this chainInfo: ret lessThan[x.value this.value chainInfo] this x chainInfo: ret lessThan[this.value x chainInfo] x this chainInfo: ret lessThan[x this.value chainInfo] private: lessThan = fn left right chainInfo: if chainInfo?prevOp == '<': ret {chainedValue<right right} else: ret {left<right right} } var a = Comparable[10] a < 5 ; returns false a < 100 < 1000 ; returns true 5 < a < 9 ; returns false

Overloading the equals operator

The = operator is a special case. Defining this operator with no parameters defines how to copy the object when it is the . Defining it with one parameter defines how to overwrite the object (when the current object is in the ) - the parameter contains the . When overriding the equals operator, note that using this = x would cause a circular reference, so if you need a way to set this, you can use meta[{}].operators['='][this x].

var object = { operator[ = ] = fn: ; normal copy operator, copies itself normally ret this value: ; normal assignment, overwrites the object itself with the new value meta[{}].operators['='].fn[this value] } ; object2 hijacks the normal use of the equals operator var object2 = { mut x = 5 operator[ = ] = fn: ret {1 2 3} ; returns a list instead of itself as its copy value: x+=value ; modifies the state of the object ; does not actually overwrite the object }

Overloading the dot operator

The dot operator is special case because while it's a binary operator, it is always defined where the defining object is the Lvalue.

Overloading the dot operator would usually be done like this:

operator[ . ] = fn varWord variable: var name = meta[variable].name ; statements
Where the varWord is a type that matches any named value, but won't match if the right side isn't a simple name.

But you can also overload the dot operator to be able to access the passed value, like a normal operator.

operator[ . ] = fn value: return value + 2

And you can combine those as well:

var someObject = { operator[ . ] = fn mut varWord variable: ; note that the variable might be undeclared and so this would be an error in that case var temp = variable + 2 var name = meta[variable].name logger.i['name: 'name] ; you can even set the variable, which would implicitly declare it if its in an object scope ; but would throw an exception otherwise variable = 5 var value: logger.i['value: 'value] } var x = 5 someObject.x ; prints "name: x" someObject.(1+2) ; prints "value: 3" someObject.y ; prints "name: y"
Note that macros passed into the dot operator don't need to use the reference-access operator because the name of the "member" is passed to it, and the value is grabbed based on the name.
var someObject = { operator[ . ] = fn name value: logger.i['name: 'name', value: 'value] } var z ~> 5 someObject.if ; prints "name: if, value: if" someObject.z ; prints "name: z, value: 5" someObject.int ; prints "name: int, value: int"

Overloading the splat operators

A function used to overload the splat operator should return an object whose iterlist will be used to splay out values.

var someObject = { 1 2 operator[ ... ] = fn: ret this.iterlist.ins[3] } {someObject...} ; returns {1 2 3} rather than {1 2} df someObject v: logger.i[v] ; outputs 1 and then 2 since someObject's iterlist is still default

Overloading the bracket operators

Bracket operators are special in that they aren't quite binary and can't chain or have an alternate order, but they aren't quite unary, and they can be used like a function call or like a macro. A bracket operator can be defined with any number of single-bracket operators ([). The function it is set to can take any number of arguments.

var someObject = { operator[ [[[ ] = fn key: ; triple bracket operator ret key+5 operator[ [ ] = fn ; bracket operator using multiple dispatch key: ret key+5 a b c: ret a+b+c } someObject[4] ; returns 9 someObject[[4]] ; returns 9 someObject[[1 2 3]] ; returns 6 someObject[[!4]] ; returns 9 someObject[[#@&2]] ; returns 20 someObject[[#@-*-]] ; takes no arguments, returns 1

The bracket operators are the only operators that can be overloaded with macros (although the dot operator can return a macro).

var object = { operator [ [ ] = macro input: ret input.split[','].map[ int.parse[v[[c: !{' ' ''@}.has[c] ]]] ].sort[v] } object[ 4,2,34,5 ] ; returns {3 4 5 34}

preOperator[ ] = function postOperator[ ] = function

Defining or overloading unary operators. Must not have any parameters. If an expression has both pre and post operators operating on it, the pre-operator is executed first.

operator[else] = fn operator args: ... preOperator[else] = fn ... postOperator[else] = fn ...

Defines a fallback, catch-all operator handler. The operator parameter is the operator represented as a string, and the args are the list of arguments the operator receives (one for unary operations, two for binary, and potentially more for bracket operations). The function should return an object to delegate operation to, or nil to indicate that the object can't execute the operation. The returned object can be the object the fallback is defined in, the other operand, or some other object entirely. This can be used to do generalized predicate dispatch.

A type is a special kind of macro that defines variables and constrains the values a variable can take on. Unlike most programming languages, types in Lima are basically contracts, otherwise known as guards. As contracts, the type of a variable almost never affects normal program execution. Exceptions to this rule are:

Like anything else in Lima, types don't have to be checked at compile-time, but will be if possible.

Operators available to all types:
a & b Type conjunction. Returns a type that has only the values both a and b have.
a $ b Type union. Returns a type that has the values of a and b.
a !$ b Exclusive type disjunction. Returns a type that has the values of a and b, but not the values they have in common.
? Returns a type that can hold nil, along with the values the type indicates it can hold. This is equivalent to rValueType $ type.set[nil].
var x = {mut int? a=5} x.a = nil logger.i[x.a] ; prints 'nil' var y = {mut int a=5} y.a = nil ; throws exception
Members of all types:
values Returns a list of all the values a type can take on. This list may be infinite, but you can always use it to check if a value matches a type by seeing if it is in this list.

Lima's interfaces are very much like "traits" in Self, or "roles" in Perl 6 in that, since normal objects can be used as interfaces, the interfaces can define implementations of the members and methods it requires and are fully composable. This allows for symmetric sum (via mixing in multiple objects), aliasing (via mix aliasing), and exclusion (also via exclusion allowed with the normal mix construct).

A type may define an explicit interface - members and methods that a value must have to match a given type. Usually this is done using the ! operator for objects - which returns a type object that defines an interface according to what members, methods, and operators that object has. This can be used for requiring that values a variable takes on has certain members with certain types - a variable declared with an interface type must contain all the public members, methods, and operators that object has.

var x = {a=5 b=6} x! x1 = {a=0 b=0} ; x1 must have the members defined in x (a and b) x1 = {a=5} ; throws an exception because the value doesn't have a member 'b'

Interfaces are also used for multiple disptach. For methods with parameters declared with interface types, a value matching that type must be passed into that parameter and the function can perform structural dispatch (or structural typing) to decide which function-path to execute for that value. This means that an object doesn't have to explicitly implement/inherit the interface, but can simply have the same structure the interface requires. This is similar to whats referred to as duck-typing and more similar to what Dart calls implicit interfaces. When an object inherits an object (via mix), it implements that object's interface by virtue of having the same privileged members, not by virtue of mixing in that particular object.

var Animal = { fn! talk ; implicitly abstract } Animal! Cat = { talk = fn: logger.i['moo'] } ; functions with multiple dispatch using types fn! func = fn Animal! obj: obj.talk[] obj: logger.i['obj is: 'obj] ; uses object's str method func[Cat] ; prints 'moo'
Note that Animal itself is not a value that an Animal! typed variable can take on. This makes sense since an interface doesn't have to implement itself.

Multiple interfaces can be inherited, even if member names conflict. Functions that take interface-typed parameters will "promote" an object that implements that interface to a form that directly conforms with the interface. The identity of an interface depends on both the original object it came from and the structure of that object. Unlike the simple duck-typing model, this allows two identically structured objects to still be treated differently.

When a multiple dispatch is being decided, first it looks to see which interfaces an object implements that has the same interface identity (the object the interface was created from), and selects based on that if possible. If its not possible, then it does structure typing (matches just the members, and not the interface identity)

var i1 = {var v} var i2 = {var v} var w = {mix[i1] v=1} ; implements i1's identity var x = {mix[i2] v=2} ; implements i2's identity var y = {v=3} ; implements neither's identity, but still matches both their interfaces var z = {mix[i1 i2:[v:v2]] v=4 v2=5} // implements both var f = fn i1! a: logger.i[a.v] i2! a: logger.i[a.v*1000] f[w] ; outputs 1 because w matches both structure and identity of i1 f[x] ; outputs 2000 because x matches both structure and identity of i2 f[y] ; outputs 3 because y matches the structure of i1 (and i1 comes before i2, so has first priority) f[z] ; errors as ambiguous - both interfaces match fully (structure and identity)

If an object inherits an interface, but a child-object omits one of the interface members, that child no longer implements the interface - even if it defines a new member of the same name.

var i = {int a} fn print = fn i! v: logger.i[v.a] v: logger.i[0] var x = {mix[i] a=1337} var y = {mix[i:[!a]] a=900} print[x] ; prints 1337 because x implements i print[y] ; prints 0 because y doesn't implement i

Keep in mind that, because an object's interface depends on its identity as well as its structure, functions that generate and return interfaces may need to use memoization to preserve the ability to use type promotion for interfaces defined in the same way.

var iGenA = fn type T: ; interface generator ret {T a list[T] b} var memberSum = fn iGenA[int~] x: logger.i[x.a + b.join[a+b]] var a = {mix[iGenA[int~][a:a2]} ; a inherits from iGenA[int~] but renames a to a2 memberSum[a] ; throws an exception
The call memberSum[a] throws an exception because a doesn't have the same structure as iGenA[int~] and can't be promoted due to the fact that each call of iGenA generates a different object (with its own unique identity).

Methods available all interface types:
cast[object] Promotes an object to having a particular interface. Returns an object where its members are mapped to match the calling interface, just like how function arguments are promoted.
var i = {var v} var x = {mix[i:[v:vee]] v=5} var xCast = i!.cast[x] logger.i[x.vee] ; prints 5 logger.i[x.v] ; prints nil logger.i[xCast.vee] ; prints nil logger.i[xCast.v] ; prints 5

The objects atr and matr are automatically and implicitly passed into either executed operator functions or modules. Properties can be set on them in a way that specifies the section of code that a given property has that value set for. The properties set on these objects are called context attributes (or just 'attributes').

An attribute is semi-constant in that it isn't mutable in a given scope, and for an attribute's value to change, it must be applied to a sub-statement. Executing a statement that mutates the value of an attribute will cause an exception to be thrown. Declaring a variable with the same name as an in-scope attribute causes an exception.

The atr object is implicitly passed into executed operator functions. It contains stack attributes. This allows you to inject information somewhere down the callstack without explicitly passing that data.

a[] ; prints "Its not red" atr[red=true]: a[] ; prints "It is red" b[] ; prints "Its not red" atr[ red=false blue=5 ; multiple attributes can be set in an atr block ]: b[] ; prints "Its not red var A = fn: printColor[] var B = fn: atr[red=false][ printColor[] ] var printColor = fn: if atr.red logger.i['Its red'] else: logger.i['Its not red']

The matr object is implicitly passed into loaded modules. It contains module attributes. This allows you to inject values into a module's dependencies. This can be used to do dependency injection of things like override IO access. For example, if you want to test a module that interacts with the network, you could inject a fake network object you can precisely control for the purposes of the tests.

Module A:
fall = fn: logger.i[matr.failMessage] exit[] ; This uses the `exitFn` attribute.
Module B:
use['./A'] trip = fn: A.fall[]
Module C:
failureMessage = 'Module B tried to exit.' fakeExit = fn: logger.i['Not today buddy!'] matr[exitFn=fakeExit failMessage=failureMessage]: use['./B'] B.trip[] ; Prints out 'Module B tried to exit.' then 'Not today buddy!', instead of exiting.

Not your mother's dynamic scoping

Attributes have similarities to dynamic scoping, since they don't need to be passed to a function to be used by it. Dynamic Scoping would have been better named "Global Scoping" since all the variables are in the global scope. Lima allows similar behavior in a much safer way than traditional dynamic scoping by making the lifetime of an attribute explicitly defined.

atr[attributeName=value]: statements atr[attributeName=value][ statements ] matr[attributeName=value]: statements matr[attributeName=value][ statements ]

Sets the value of a set of attributes for a block of statements. Brackets are optional if only adding the attribute to one statement.

atr.attributeName matr.attributeName

Returns the value of the attribute. Throws an exception if the attribute hasn't been initialized in this call scope.

nil is a reference to nothing. This is not the same as the value 0. Only nilable types (eg types with a question mark after them) can hold .

Like everything else in Lima, nil is an object, and has operators:

=

nil's copy operator returns a value that's identical to actual nil except it also contains an assignment operator:

=

Copies the value of into . = is 'backward' (right-to-left associative) and returns a reference to the newly assigned value.

x = 5 ; sets x to 5 a=b=c=d = 0 ; sets a, b, c, and d all to 0

~>

Points a variable at a another variable or value - making a reference.

var x = 5 mut y ~> x ; y now points to x logger.i[y] ; prints 5 y = 4 ; x is now 4 logger.i[x] ; prints 4 logger.i[y] ; also prints 4

~> is 'backward' (right-to-left associative) and returns a reference to the value being pointed to. E.g. a~>b~>c~>d ~> x ; sets a, b, c, and d all to point to x. ~> can also be used as a unary postfix operator. This is useful when the reference being modified might point to a macro (and IDEs should recognize when someone may have used ~> as a binary operator when they should use the unary operator, and warn the user appropriately). E.g. a~> x ; sets a, b, c, and d all to point to x. Under the hood, this postfix operator returns a macro that points the reference to the expression following it.

~>> Just like ~> except creates a weak reference.
value?

Safe traversal postfix operator. If value is nil, returns an empty object with the following things that distinguish it from a normal empty object:

  • It has a bracket operator that returns nil if the property doesn't exist (instead of throwing an exception), and returns the existing value otherwise.
  • If copied, a normal empty object will be returned. Note that most of the default methods on objects (ins, sort, cat, etc) create copies of the object.

If value is not nil, it returns the value.

Note that this (like most of nil's operators) are inherited by other objects, so the case where the value is not nil is important.
This can help you safely access nested members of an object without intermediate nil-checking.
x = { a:{f:'hi'} } x['a'] ; retursn {f:'hi'} x?['b'] ; returns nil x?['a' 'f'] ; returns 'hi' x?['a' 'h'] ; returns nil x['a' 'h'] ; would throw an error x?['a']?['h'] ; returns nil x?['a']['h'] ; would throw an error y = x? y['b'] ; would throw an error (because it was copied from x?) y?['b'] ; returns nil x?['b' 'h'].ins[3] ; returns {3}
?name

Safe traversal infix operator. If is nil, returns nil. If is not nil, returns Lvalue.name

This can help you safely access nested members of an object without intermediate nil-checking.
x = returnNil[] x?y ; returns nil x?y?z ; returns nil x.y ; would throw an error x.y.z ; would throw an error
a | b
a ?? b Returns true if a and b represent the same data (they hold the same piece of data in memory). Examples of this are when the same value is passed into two different parameters in a function, or if you're comparing what two references point to.
a !?? b Identical to !(a ?? b)
a == b Returns true if both values are the same (ie they're both nil).
a != b Not-equals. Identical to !(a == b).

{ }

An object literal. Can be used for making objects, lists, associative arrays, matrices, etc. There are three ways to add members in an object liberal:

  1. Adding values with implicit keys. E.g. {'a' 'b' 'c'}
  2. Adding properties with literal values as keys using the : operator, for example: {14:'hello' "b":4 5.9:0.1}. This is the same as setting values with literal keys the default bracket operator, e.g. that last object is the same as x at the end of this code (except that it might not be mutable):
    mut x={} x[14]='hello' x['b']=4 x[5.9]=0.1
  3. Adding named properties using the : operator, for example: {a:3 b:8}. This is the same as using string literals: {'a':3 'b':8}.
  4. Adding properties with keys from evaluated expressions using the :: operator, for example:
    var a = 4 {a::'hi'} ; Same as {4:'hi'} {3+4::'yo'} ; Same as {7:'yo'}
  5. Adding privileged members with = (and any other operation that sets a value on the calling scope), for example: {a=1 b=2 c=3}. Creates a member. This is the only way private members can be created in an object literal.
    var x = {a=4} ; a is privileged here and can't be accessed via the default bracket operator ; are different var z = {a:4} ; a here is not privileged
Any value can be a key, and keys are always differentiated using their hashcode first and their equality operator (==) second. Members defined using option 5 above have the type var by default (if no type is given). Members defined using option 1-4 above have the type var? (ie they can be nil), are mutable by default (as long as the object containing them is mutable), and can't be private.

Any statement can be run inside an object's scope, but any expression that resolves to nil will not add any member to the object.

Members of an object can't normally be overwritten inside an object literal.

{a=1 b=2 a=3 ; throw an exception } {'a':1 3:2 'a'=3 ; throw an exception }

The override macro can be used to over write an object member inside its literal, which is useful in cases where an object inherits from another object.

var parent = {a=1} var child = {mix[parent] override a=9 ; doesn't throw exception! }

Note that setting a member of an object with : is like using the bracket operator.

; two ways to write the same list: {10 15 20 25} ; a list {0:10 1:15 2:20 3:25} ; the same list { x=2 ; An object with privileged members. y=89 } {'r':5 'u':2 34:'falcon'} ; an object/associative array { {1 2 3} ; a matrix (list of lists, or more accurately, nested objects) {4 5 6} {9 9 3} }
All three ways to add members can be combined, as long as all the values with implicit keys come first.
{ 1 2 3 'a':b x=5 y = fn: ret 'y' }
The identifier _ is a special identifier that, when used as a value in an object literal, stands for the in-scope variable with the same name as the key. This can be used to set properties on an object in a DRYer way.
var someLongName1 = 5, someLongName2 = 10 var x = {someLongName=_, someLongName2=_} ; same as {someLongName=someLongName, someLongName2=someLongName2} var y = {someLongName:_, someLongName2:_} ; same as {'someLongName':someLongName, 'someLongName2':someLongName2}
Value initializations set in the never reference the value of any member of that object directly. Variable names refer to variables in the scope in which the object is being created up until an alias is created inside the object.
var a = 5 var object1 = { x = a ; Declares x and initializes it as 5 a = a+11 ; Errors because it declares a before the r-value is evaluated, and the inner a hasn't been initialized yet. b = this.a ; sets b to 16 c = a ; throws exception because of ambiguity }
Also note that the order of object keys are the same order as in the object definition, so things like looping over an object will loop in the order they were defined in.

Object members are public by default. Also, objects in the same (same file) can freely access each others' private members (despite their private qualification).

members can't be accessed via the bracket operator and won't show up in the object's IterList (and so won't appear as a member when iterating through the object in a loop). All private variables are .

var x = { a=5 B=6 } x['a'] ; nil x['b'] ; nil x['B'] ; also nil ; The loop's body will never be called because there aren't any properties in the object. df x v: throw "Shouldn't get here"

Objects inherit from nil an so get all the operators defined for nil. The following operator is overridden:

Binary operators defined for objects:
objectmemberName

Accesses a member of the object. memberName will be interpreted as a name (rather than using its value if it happens to also be the name of an variable in scope).

The operation will throw an exception if no privileged member exists.

a + b Matrix and vector addition (adds elements with the same index together). Equivalent to a.map[v+b[k]]. For matrix/vector addition and subtraction, and the vector dot product, a and b must have the same dimensions and size. An exception is thrown if they don't.
a - b Matrix and vector subtraction (subtracts elements of b from a that have the same index). Equivalent to a.map[v-b[k]].
a * b

Matrix product. Only defined for rectangular matrices when the number of rows in a is the same as the number of columns in b and vice versa.

a / b Product of inverse matrices. Defined only where b is an invertible matrix. Also defined only where either the matrix product of a on the inverse of b is defined, or where a is 1, in which case the operation indicates the inverse of b.

a & b Set intersection (conjunction). Returns a that contains the member-values that exist in both a and b. Keys are not preserved.
a $ b Set union (disjunction). Returns a that contains the member-values that exist in either a and b or both. Keys are not preserved.
a !$ b Set exclusive disjunction. Returns a that contains member-values that exist in only a and member-values that exist only in b but not member-values that exist in both. Keys are not preserved.
a && b Object member intersection (conjunction). Returns a copy of b that only has keys that exist in both a and b. This essentially returns b with keys removed if they don't exist in a.
a $$ b Object member union (disjunction). Returns an object that merges b into a. Any members that exist in both objects will contain the value that exists in b.
a !$$ b Object member exclusive disjunction. Returns an object that contains member that only exist in a and members that only exist in b but not member-values that exist in both.

a == b

Value equality. Returns true if both a and b contain all the same keys, and if the values at those keys are all the same (when compared with the == operator).
a <> b Set equality. Returns true if all the values contained in a are also contained in b. Since types are treated as sets in Lima, this can also be used to compare equality of types (meaning comparing two variables containing types, not comparing the types of two variables).
a !<> b Same thing as !(a <> b)
a < b Proper subset of s (returns true if a is a proper subset of b).
a > b Proper superset of s (returns true if a is a proper superset of b).
a <= b Improper subset of s (returns true if a is an improper subset of b).
a >= b Improper superset of s (returns true if a is an improper superset of b).
x << >> <<= >>= o

Key-aware comparisons. Just like their counterparts <, >, <=, and >= but compares keys as well as values.

{a=1 b=2} << {a=1} ; true {b=1} < {a=1} ; true {b=2} << {a=1} ; false


a || b Combines two lists by interleaving their elements by the time they were added. Only works on lists that are only appended to.
+= -= *= /= &= $= !$= &&= $$= !$$= |= ||= All of these operators are compound assignment operators where the lvalue is set to the lvalue and rvalue operated on with the operator without the = at the end. For example, a+=b is equivalent to a = a+b, and a|=b is equivalent to a = a|b.
.= Same idea ^. The result of the expression following the operator is assigned to the calling object. Also can't be chained - you can't have two .= operators in the same expression. x.=y.=z will throw an exception.
someObject.=x ; same as someObject = someObject.x someObject.=function[3] ; same as someObject = someObject.function[3] someObject.=x.y.z[4].f ; same as someObject = someObject.x.y.z[4].f someObject.y.z.=help.ok[5].go ; same as var temp ~> someObject.y.z temp = temp.help.ok[5].go
[= ] [[= ]] Same idea as the dot-equals operator. The result of the expression following the operator is assigned to the calling object. Also can't be chained.
someObject[=x] ; same as someObject = someObject[x] someObject.y.z[=help].ok[5].go ; same as someObject.y.z = someObject.y.z[help].ok[5].go

Unary operators defined for objects:
~

Reference access operator. For normal objects, simply passes the value right back.

The splat operator (inspired by coffeescript), aka the spread operator, is used to splay values in an object out as arguments to a function or values in an object literal. It is also used as syntax for varargs in functions (variadic functions).

A () can be used to pass an object's members in as function arguments.

x = fn a b c: logger.i[a b c] y = {1 2 3} x[y...] ; outputs "123" z = {b:1 a:2 c:3} x[z...] ; outputs "213" using z's members as named parameters

Only one splat can appear in an argument list, but it can be written between arguments if need be. Splats must come after any variables with default values.

fn a b=5 c splat... d e f: logger.i splat[[cat]]

Splats can be used in objects (and destructuring assignments).

var x = {2 3 4} var y = {a:8 b:9} var a = {1 x... 5} ; gets {1 2 3 4 5} var b = {y... c:10} ; gets {a:8 b:9 c:10} var[m j other] {m j other...} = a ; same as m=1 j=2 other={3 4 5} {var[9:t 8:u 7:v]} = {9:'h' 8:'e' 7:'y'} ; same as t='h' u='e' v='y'

One might ask how using splats in an object literal compare to . The answer is that splats only splay out normal (non-) members, while includes members as well.

Interface operator. Creates an interface type that has all the members that the object has. Returns a type (defined by the object's interface).

Members available by default for any object:
str The default to-string method returns a string of code for an object-literal containing each non-privileged member in the object.
len The number of elements in the list. This member cannot be set(written to). Is undefined for objects that aren't lists (any object that has had keys modified or added that aren't between 0 and list.len inclusive).
keys This returns a list containing all the keys of the non- public members in the object. One way this can be used is to test if an object contains a certain member. For example, object.keys.has[34] returns true if the object has a member/element 34. The order of the keys in this member determines the order that the object is looped through. This member can be modified to rearrange the keys, but cannot be used to add or remove keys.
iterlist This returns this by default. Since calls iterlist, df will just iterate through the object as one would expect - unless this member is overridden.
hashcode The value in this member (or pseudo-member) determines how to use the object as a key for an object property. Most often, this should return something related to how its == operator decides what is equal to it. In cases where you want two objects that test as equal to each other to be used as separate keys in a map, the objects need to return different values from hashcode. The basic value for a hashcode is an integer, but any value that has a hashcode member can be returned as a hashcode value (non integer values that can be used as keys have a hashcode member that returns an integer. Objects that disinherit hashcode and don't define their own can't be used as keys.
Special members only available inside object literals:

The following two members reference aspects about the object literal they're written in. They are special in that they break the rule of not allowing duplicate aliasing in Lima. This is one of the very few cases where Lima has a construct that can't be duplicated by the programmer.

this

Read-only member that references the object itself. This will act just like using the object outside itself. Private members can't be accessesed using this.

this references the inner-most object it's used in or the object the function was inherited into (if used in an inherited function):

var x = { mut n=0 func2 = fn: n++ ret this.n ; Will access whatever public n is available on the object inheriting this method. } var y = { mix[x] n = 'hi' } logger.i[x.func[]] ; prints 1 logger.i[y.func[]] ; prints 'hi' var z = { 1 2 3 a=3 b=this[1] ; b gets 2. }
Methods available by default for any object:
has x Tests if the object contains x, where x is some lima expression. obj.has[x] is essentially is the same as {x} <= obj, but is more readible.
cat list lists Concatinates lists. It returns a data structure, where the variables and constants are concatenated in the order they're listed.
{1 2 3}.cat[{4 5 6}] ; returns {1 2 3 4 5 6} 'I'.cat["'m a mo" 'ose'] ; returns "I'm a moose"
find sequence

Returns a list of s where each member in the list is a non-overlapping of the object with the same sequence of elements as sequence.

{1 2 3 4 5}.find[{3 4}] ; returns { {2:3 3:4} } "What are you looking at?".find["at"] ; returns two string slices that looks like { {2:'a' 3:'t'} {21:'a' 22:'t'} }

split sequence includeSplitPoints

Splits the list into a list of s that are separated by one of the passed in sequences in the original object. includeSplitPoints is optional (default false), but if true, the elements matching the sequence are included in the begginning of the next .

{1 2 3 4 5}.split[{3 4}] ; returns { {0:1 1:2} {4:5} } "What are you looking at?".split[" "].map[v.sort[k]] ; returns {'What' 'are' 'you' 'looking' 'at?'} "What are you looking at?".split[" " true].map[v.sort[k]] ; returns {'What' ' are' ' you' ' looking' ' at?'}

replace sequence replacement This function returns a new object where every occurrence of sequence is replaced with the replacement.
var y1 = {1 2 3 4}.replace[{2 3} {90 91 93}] ; y1 gets {1 90 91 93 4} ; is equivalent to: mut y2 = x var sequence = y2.find[{2 3}] ; find the sequence y2.=rm[[sequence.keys.has[k]]] ; remove it df {90 91 93} value key: y2.=ins[key value] ; insert the new sequence logger.i["What's going on in here?".replace["in" "down"]] ; prints "What's godowng on down here?"
tslice start end

Returns the list elements of list that were created at or after the time represented by start and before (but not at) the time represented by start. The parameters start and end can either be time objects or futures (or a combination). If an argument is a future, the time it represents is the time the future is resolved (when its wait function would be called).

This is useful for implementing event-like code (see "Event handling in Lima" under Concepts for more info).

Defined as the following:
tslice = fn startTime endTime: var start = meta[this].history[startTime] var end = meta[this].history[endTime] var d = dif[start end] if d.change != nil $ d.set != nil ; can't have been set or have other inner changes $ d.elements[[v.type=='move' $ v.type=='remove']].len > 0 ; can't have had elements moved or removed $ d.elements.all[v.index >= start.len]: ; elements should all have been appended throw "tslice can only work on a list that is only appended to between the given times" var indexes = d.elements.map[v.index] return end[[indexes.has[k]]] ; returns slice
tslice end Same as:
var now = time.before: list.tslice[now end]
dot x Dot product. Only defined for vectors (lists of numbers). a.dot[x] is equivalent to a.map[v+b[k]].join[v v2: v+v2].
cross x Cross product (of vectors). Only defined for vectors (lists of numbers) that have 3 dimensions.
Macro methods available by default for any object:
key1 key2

Accesses a non-privileged member of an object/list. With only a single input key, it simply accesses that member. Multiple keys can be input, which would result in accessing a member of a member. For example, object['a' 'b' 'etc'] is the same as object['a']['b' 'etc'].

Keep in mind that if one of the sub-members has an overloaded bracket operator, this equality still holds.
obj = { a = fn x y: ret {x y} } obj['a' 'b' 'c'] ; returns {'b' 'c'}

If the key doesn't have a value set for it, the operation returns a setter that can be set using the = operator. If used with any other operator (including being the rvalue of an = operation), an exception will be thrown.

The bracket operator cannot access (read/write/create/etc) members of an object. When the bracket operator is used to set a key that is already a member of the object, a new non-privileged member will be created and that because there is a privileged member of that name too, the non-privileged member can no longer be accessed via the dot operator. This is because privileged members are in a different namespace from properties.

var x = { Funco = "Ok!" } logger.i[x['Funco']] ; errors because the key 'Funco' has never been set, and the bracket operator can't access read-only members logger.i[x.Funco] ; outputs Ok! x['Funco'] = 4 ; sets the 'Funco' key logger.i[x['Funco']] ; outputs 4 logger.i[x.Funco] ; still outputs Ok!

Also note that the bracket operator's keys are case sensitive (for string keys). For example,

var x = { theCar = "deLorean" } logger.i x["theCar"] ; again outputs deLorean ;logger.i x["thecar"] ; would throw an error because the key "thecar" has not been set x["thecar"] = "acura" logger.i x["theCar"] ; again outputs deLorean logger.i x["thecar"] ; outputs acura

This also works for lists and matrices (mult-dimensional arrays).

matrix = { {1 2 3} {4 5 6} }
matrix[1 2] accesses the that has the value 6.

Inside the brackets, the e is available and contains the 'end' index (the list's length minus 1). Contains nil if the object isn't a list (if it contains properties that aren't elements). The e can be overridden with a different name by adding the alternate name before a colon inside the brackets. For example:

var secondToLastElement = obj[lastIndex: lastIndex-1]

Note that the hashcode used to map keys to values is the integer return value of the object's hashcode property.

Ins index sequence...

Returns an object with the sequence inserted starting at the int key index. If any values are already at indexes that will be written with values in the sequence, they will be removed and reinserted in the next key after the sequence (index+sequence.len). This causes all the values at consecutive indecies at or higher than index to be "moved over" to make room for the new value. If index it is inserting at isn't an integer, an exception will be thrown.

var a = {9 8 7 6} var b = a.ins[4 4] ; b contains {9 8 7 4 6} var c = b.ins[3 1 2 3] ; c contains {9 8 7 1 2 3 4 6}
If the parameter index is omitted (and there's just one parameter - a single value to insert), that single-value sequence will be appended to the list (inserted at the "end" - the first unoccupied positive integer index closest to 0 in the object).
var a = {} mut b = a.ins[1] ; returns {1} b.=ins[4] ; 4 is appended to b - b is now {1 4}

Has the e in the same way the bracket operator does.

rm indices..

Returns a list with the selected member removed and the list rekeyed. Selection works just like the (eg x.rm[a b] is the same as x[a].rm[b].

var x = {1 2 3 4 {5 6}}; x.rm[3] ; returns {1 2 3 {5 6}} x.rm[4 1] ; returns {1 2 3 {5}} x.rm[e] ; returns {1 2 3 4} x.rm[e-1] ; returns {1 2 3 {5 6}} mut y = {59:10 69:20 79:{34:2 35:3 37:4} 89:40} y.rm[79 34] ; returns {59:10 69:20 79:{3 4} 89:40} y.=rm[79 34] ; sets y to the new value that doesn't contain that element

sliceConditionals

Object slicing based on conditions. Also known as a filter (e.g. in python, javascript, haskell) or where (e.g. C#, sql). Returns a showing the members that match the condition. A is an object whose members are references to the corresponding members in the original object (this is explained more below). Keys preserve.

In the , k and v are s unless different aliases are declared.

  • v refers to each value in the list, and
  • k refers to each key in the list.
Different aliases (than v or k) can be declared by writing one or two variable names and a colon right after the opening bracket (e.g. list[[value key: key%2 == 1]] and list[[value: value < 1]]).
{1 50 4 99}[[k<50]] ; returns a slice with all members whose keys are less than 50 (a slice that looks like {0:1 2:4}). logger.i['Delorean Motor Company'[[{0 9 15}.has[k]]]] ; prints 'DMC' ; the following returns a slice with all members whos third element is less than 50 ; (a that looks like {0:{1 2 3} 2:{6 7 8}}). {{1 2 3}{4 5 50}{6 7 8}}[[ v[3] < 50 ]] ; the following returns a that could represent a matrix where the columns (the first order members e.g. {1 2 3}) ; only contain the rows where the element in the third column of that row is less than 50 (resulting in a that looks like {{1 2}{4 5}{7 8}}. x = {{1 2 3}{4 5 6}{7 8 50}} x[[v[3] < 50]]

Note that if any aliases are declared (if only one or two names are written before the colon), then the undeclared aliases are not available. If any of the variable names v or k are already defined in scope, then you have to declare at least one alias (and it can't conflict with any other name in scope).

var[k={'a' 'b' 'c' 'd'}] ; gets every other character in the list k the value alias must be defined here because otherwise k would be implicitly declared ; and would error because of duplicate variable names k[[val: val.ascii%2 == 'a'.ascii]]

Similar to the bracket operator , multiple sliceConditionals can be written to access deeper dimensions of an object.

var x = {a=1 b="hi" c={1 2 3} d={40 51 60}} x[['c'<=k<='d' v%2==0]] ; returns {c={1:2}, d={40 2:60}} x[['c'<=k<='d']].map[ v[[val: val%2==0 ]] ] ; this does the same thing in a less elegant way

Slices

As mentioned above, a is an object whose members are references to the corresponding (non-privileged) members in the object the slice was taken from. This is a conceptual extension of the single-bracket-operator (single member access) to sets of members. If you modify a member of the , the corresponding member in the original list will also be modified, and vice versa. As with normal references, setting a variable (with the = operator) to a slice will give that variable a copy of those members (not references to them). However, if you make a variable reference the slice instead (or directly use the result of functions/operators that return slices), you can modify the original object with that reference.

var x = {1 2 3 4 5} var y = x[[v > 3]] ; y gets a copy of the slice containing the first two members (1 and 2) y[3] = 99 ; x remains the same because y got a copy y ~> x[[v > 3]] ; y is referencing the slice y[3] = 99 ; x is now {1 2 3 99 5} df x[[v<3]] v: v++ ; after that loop, x is now {2 3 3 99 5} - the first two members were incremented
While the values in a slice are references to the original values, the keys are copies and so can be manipulated however.
var x = 0..100 ; sets the last value greater than 50 and less than 80 to 'hi' x[[v>50]].sort[k][0] = 'hi' ; after this, x[51] holds 'hi', but is otherwise unmodified
Note that not only can changing a list-slice affect the original object, but since the values in a slice are references, changing values in the original object can affect the slice.

A slice inherits members from the object it is a slice of, meaning that it will have all the same methods and operators as the original object. Most standard methods and operators can't affect the original object the slice was taken from , but some can.

. [] [[]] [? ] ... All of these operators return references to the object the slice was taken from, and thus members of the results can modify the original object.
var x = {a=1 b=2 c=3 d=4} x[[v<3]].b = 5 ; x.b is set to 5 var y = {1 2 3 4} y[[v>=2 & v<5]][2] = 99 ; y now holds {1 2 3 99} var f = fn mut[a b c]: a++ b++ c++ var z = {1 2 3 4 5} f[ z[[k>=2]]... ] ; after this executes, z contains {1 2 4 5 6}

map memberStatment

Returns the result of performing some statement on every member (value or key/value pair) in a list-object. The memberStatment tells how to modify the value. The s v (for value), k (for key), and c (for count of how many values have been looped through) are declared by default, but can be aliased just like with the double-bracket operator.

{1 2 3}.map[v*5] ; will return {5 10 15} {1 2 3}.map[{2 3 4 5}.has[v]].join[a&b] ; asks if every member in the list is either 2, 3, 4, or 5 {false true true}.join[a&b] ; is equivalent to the above (thus it will return false, since 1 isn't in the list). {1 2 3}.map[{2 3 4}.has[v]].join[a$b] ; similarly, this asks if any member in the list is either 2,3,4, or 5 (so this will return true).

map keyStatment memberStatment

Just like map with one statement except that it also modifies the key-value. The first parameter, keyStatment, modifies the key, and the second parameter memberStatment modifies the member's value.

{1 2 3}.map[v v^2] ; returns an object keying the squares of the values by the original values: {1:1 2:4 3:9} {a=1 b=2 c=3}.map[c k] ; basically, returns {0:'a' 1:'b' 2:'c'} (basically the same thing as {a=1 b=2 c=3}.keys)

join expression

AKA reduce or fold, join combines all the values in an object into one value. The expression determines how to combine the elements of the list. The s in the expression are a and b. In the first combination, a holds the first elements and b holds the second element. After the first combination, a holds the current aggregate, and b holds the next element to aggregate. This means that the aggregate should be a similar type of value as each element (as is the case with sums or concatenating strings). Throws an exception if theres an empty list.

{1 2 3}.join[a+b] ; returns 6 {}.join[a+b] ; throws exception {{x=1} {x=2} {x=3}}.join[a + b.x] ; errors because in the expression {x=1} + {x=2}.x, {x=1} does not have a + operator {{x=1} {x=2} {x=3}}.join[a.x + b.x] ; errors because in the expression 3.x+{x=3}.x, 3 does not have the member 'x'

join init expression

Similar to join with one argument, except a always holds the current aggregate (which is initialized to init) and b holds the next element to aggregate. Empty list returns the init value

{1 2 3}.join[0 a+b] ; returns 6 {}.join[0 a+b] ; returns 0 {{x=1} {x=2} {x=3}}.join[0 a+b.x] ; returns 6

scan expression

Like join with one argument, but returns a list of all intermediate values in the join. x.join[exp] could be implemented as x.scan[exp].sort[-k][0] - getting the last value in the list returned by scan.

{1 2 3}.scan[a+b] ; returns {3 6}

scan init expression

Again, similar to join of this form (two arguments), but returns a list of all intermediate combinations in the join.

{1 2 3}.join[0 a+b] ; returns {1 3 6} because the first combination is 0+1 {}.join[0 a+b] ; returns {0} {{x=1} {x=2} {x=3}}.scan[0 a+b.x] ; returns {1 3 6}

split[[ ]] expression

Returns a list of s split at points where the expression is true. The s v (for value), k (for key), and c (for count) are declared by default, but can be aliased just like with the double-bracket operator. The elements where the expression is true are left out.

{1 9 10 3 1}.split[[v<10]] ; returns {{1 9} {3:3 4:1}}

split[[ ]] expression includeSplitPoints

Just like split with one parameter, except if includeSplitPoints is true, the elements where the expression is true (the split points) are left *in* at the beginning of every in the resulting list except the first.

{1 9 10 3 1}.split[[v<10 true]] ; returns {{1 9} {2:10 3:3 4:1}} aList.split[[v!=aList[k-1] true]] ; splits when value changes

all expression

Returns if expression is true for all members. Uses the same s as map. Returns true for an empty object.

obj.all[expression] ; equivalent to obj.map[expression].join[true v&v2]
group [expression]

Returns a map where each key is the value that the expression was evaluated to, and each value is an array of values in the list that all cause the the expression to evaluate to the same value. Keys of lists preserve.

Group uses s just like map and the slice operator.

{ 1 2 3 4 5 4 3 2 }.group[v] ; returns { 1:{1} 2:{1:2 7:2} 3:{2:3 6:3} 4:{3:4 5:4} 5:{4:5} } var x = {x={a=1 b=2} y={a=3 b=5} z={a=1 b=9}} var y = x.group[v.a] ; sets y to { 1:{x:{a=1 b=2} z:{a=1 b=9}} 3:{y:{a=3 b=5}} } var x = { {1 2 3 4} {1 5 8} {9 10 11} {9 11 15} {20 18 17} } x.group[v[0]] ; returns { 1:{0:{1 2 3 4} 1:{1 5 8}} 9:{2:{9 10 11} 3:{9 11 15}} 4:{4:{20 18 17}} }

rm[[ ]]

Returns a list with the members matching the removed and the list rekeyed. The sliceStatement works just like for .

var x = {1 2 3 4 5 6}; x.rm[[2<v<6]] ; returns {1 2 6}

ins[[ ]] value testExpression

Returns a new list where for every set of elements, value has been inserted between them as long as testExpression returns true for those elements. testExpression is optional, defaulting to true.

{1 2 3}.ins[[0]] ; returns {1 0 2 0 3}

a, b, ak, and bk are s, and they are aliased in that order. a is the value that comes first in the list, and ak is its index. b is the value that comes second in the list, and bk is its index.

mut x = {1 2 3} x.ins[[a+b-ak]] ; returns {1 3 2 4 3} x.ins[[prev next key1: prev+next-key1]] ; the same thing using different aliases x.ins[['a' a+b<4]] ; returns {1 'a' 2 3} x.=ins[['hi' ak==0]] ; x gets changed to {1 'hi' 2 3}

sort[ ] objectMembers

Stable value sort, ascending. Returns an object where the members are sorted by some values inside the brackets. v and k are s.

It will be primarily sorted by the first value, secondariliy sorted by the second value, etc. Order is preserved when values are equal. The keys will be changed so that all the values are elements (their keys all start from 0 and increment from there).

list x = {8 3 9 4 87 1} x.sort[v] ; returns {1 3 4 8 9 87} var obj = {a=1 b=-2 c=3} obj.sort[val key: obj.len-key-1] ; returns {3 -2 1} {{a=5 b={x=9} c='c'} {a=5 b={x=7} c='d'}}.sort[v.a v.b.x] ; returns {{a=5 b={x=7} c='d'} {a=5 b={x=9} c='c'}}

sortus[ ] objectMembers

Unstable sort. Exactly like Sort[ ], but doesn't ensure that order is preserved when values are equal (the 'us' of 'sortus' stands for "unstable").

sort[[ ]] objectMembers

Stable expression sort, ascending. Returns an object where the members are sorted based on comparison expressions inside the brackets. The aliases a, b, ak, bk refer to the keys and values of the two items being compared by the sorting method. The expressions should indicate whether a comes before b.

Order is preserved when values are equal. It will be sorted primarily using the first comparison expression, secondariliy using the second comparison expression, etc.

list x = {8 3 9 4 87 1} x.sort[[a<b]] ; returns {1 3 4 8 9 87} obj.sort[[val1 val2 key1 key2: val1<val2 ]] ; also returns {1 3 4 8 9 87}

sortus[[ ]] objectMembers

Unstable expression sort. Exactly like Sort[[ ]], but doesn't ensure that order is preserved when values are equal (the 'us' of 'sortus' stands for "unstable").

Special constructs that can be used inside the of an object:
static: static [ ]

Static code run when the object literal's value is created. The are executed on creation of the object (not on copy/inheritance of the object). Any variables in the static block are private object members, and so are inaccessible outside that object. This is like the static block in Java and C++.

A static block's statements are run:

  • after any declaration block lines that are before the static block
  • before any declaration block lines that are after it

For modules, static can be used to initialize things the module might need, for example adding time standards or string encodings.

[ targetObjects]

Declares the platforms and formats the object is intended for. If the object is compiled, it will be compiled into files of different formats based on its targets (keep in mind that modules are themselves objects).

Platforms and formats include optimized code for different languages, readable code for different languages, assembly code output for different architectures, and executable native or byte code in different forms (executable, static library, or dynamic library) for different platforms. Examples of targets: ppc.mac, x86.win32, x86.linux, x86.mac, x86.win32.o, mips, java, llvm.

Is ignored unless the object is the entrypoint being compiled.

Base-10 integer. Digits can be grouped using single-quotes. For example:
var x = 34 var y = 10000003459999 var z = 100'234'346'430 var crazy = 10'''234'3543454''45 ; same as 10234354345445
x Integer written in base . This can be used for binary or hex, or any (integer) base from base-2 up to base-36. For bases greater than 10, letters are used for digits higher than 9 (case insensitive). Bases higher than 36 are not supported in this literal format. Digits can be grouped using single-quotes.
var w = 2x101 ; binary - x gets 5 var x = 16x1A ; hex - x gets 26 var y = 20xD3A0F495B ; base-20 var z = 36xzf8943lifpwe ; base-36 var a = 20x'be8k'2305'm876
Base-10 real number. Digits can be grouped using single-quotes.
var x = 1.235 var y = .0845 var z = 10'230.2343'4634'5
x Real number written in base . Digits can be grouped using single-quotes.
var x = 2x1.1 ; 1.5 in binary var y = 16x1.8 ; 1.5 in hex var z = 32x1.0000'g000'a53
Stands for infinity. This can be used for creating infinite lists or testing mathmatical operations.
var x = 0..00 ; returns an infinite list starting with 0

Numbers are objects, but they don't share many members with default object literals. They have more number-specific members. The following operators are overridden:

And the following operators are not inherited.

Members available to all numbers:
str The to-string method returns the number in simple base-10 format.
bits An array of boolean values representing the calling number, that also has special methods and operators for bit operations. Bit arrays are structured in little-endian order (least-significant digits first - so 2x0001.bits[0] == 1). This is essentially the way to write a bit-array immediate, for example 16xF041.bits returns a the bits with the bits 1111000001000001. The length of the bit array is the minimum length to store that number. Only defined for positive integers (use ones or twos for negative numbers, or use another encoding).
mut x = 2x110001.bits x.ins[0 0 0 0] ; left-shift 3 times, like stack pushes, returns 2x110001000 x.=rm[[[k<2]].sort[k] ; logical right-shift twice, like stack pops, x becomes 2x1100 ; most likely, you'll never need to write an arithmetic right-shift: var signBit = x[x.len] var signBitTwoTimes = 1..2.map[signBit] {signBitThreeTimes... x[[v<2]].sort[k]...} ; returns the number arithmetically right-shifted twice x.map[v&y[k]] ; retuns x's bits anded with y's (to work properly, x and y should be the same length) x.map[!v] ; one's complement of the bit array

bits objects have the following members:

num Returns the positive numeric value of the bit array. Errors if the array is empty.
ones Returns the signed numeric value of the bit array interpreted as the one's complement number representation. Errors if the array is empty.
twos Returns the signed numeric value of the bit array interpreted as the two's complement number representation. Errors if the array is empty.

bits objects have the following methods:

dec encoding Decodes the bits into a string with the encoding string.encoding[encoding].
chunk chunkSize Returns an array of integers made from chunks of the bit array. Equivalent to bits.group[k//chunkSize].map[v.join[0 v+v2]]
dechunk intList Returns a bits object made from the passed list of integers. Equivalent to bytes.join[bits.none v.ins[v2.bits]]

sign This returns -1 if the number is negative, and 1 otherwise.
error Returns a probability object describing the probabilities that the number has accumulated calculation error from calculations done on the original errorless values it derives from. The calculation error is in absolute deviation from the true value. This can be used to specify the intended precision of numeric operations.
mut real x = 5.43 df 10^4..10^5 n: x *= 1+1/n assert[x.error.sum[[.99 < k/k < 1.01]] == 1] ; assert that the resulting approximate number is always within 1% of the number's true value
Without any assertions like this, the program will calculate exact values, so asserting things about the error will usually make the program less precise (which may allow it to be optimized in some way). However, other assertions later can constrain the error of a value in such a way that it will force a value it derives from to be more precise than it would otherwise need to be.
Methods available to all numbers:
ones length Returns a bit array representing the (potentially signed) number in one's complement representation for a bit-length of length. Will throw an exception if the number can't fit into the given length.
4.ones[5] ; returns 2x00100 -4.ones[5] ; returns 2x11011
twos length Returns a bit array representing the (potentially signed) number in two's complement representation for a bit-length of length. Will throw an exception if the number can't fit into the given length.
4.twos[5] ; returns 2x00100 -4.twos[5] ; returns 2x11100
mod num True modulus (x.mod[n] is the absolute value of x%n)
Binary operators defined for numbers:
a + b Addition.
a - b Subtraction.
a * b Multiplication.
a / b Division. Note that divide by zero throws an exception (it does not return any kind of NaN-type value).
a // b Discrete division (a / b rounded down to the nearest integer)
a % b Modulus, returns the remainder of a//b. This works just as well with rational numbers - it returns a (possibly fractional) remainder after integer division is performed. For example, 4.4%0.8 returns 0.4
a ^ b Exponential power (a to the power of b). This operator is one of the few that are left associative (ie 2^3^4 does not mean the same thing as (2^3)^4)
a b Returns a list of all integer values from a through b (inclusive). For example, 2..5 returns {2 3 4 5}
a ... b

Creates a list representing the fractional range from a to b (inclusive). The list is essentially an uncoutable set. The list is conceptually continuous and so it's length will always be 00. Attempting to access any other index other than 0 will throw an exception (accessing index zero gives you the first value in the range).

While fractional ranges can't be written discretely, they can still be operated on. For example,
var range = 2...5 range.has[2] ; returns true range.has[2.34656] ; returns true range.has[1] ; returns false 3...4 < range ; returns true 3..4 < range ; also returns true .4 ... .3 < 0...1 ; returns true .4 ... .3 < 0..1 ; returns false since 0..1 only contains 0 and 1
a & b Logical and. Only defined for values 0, 1, true, and false. The operators &, $, and !$ don't do short circuit evaluation like they do in most Fortran-family languages (although short-circuit evaluation may happen under the hood as an optimization).
a $ b Logical or.
a !$ b Logical xor (exclusive or) returns a&!b $ !a&b.
a == b Returns true if a represents the same numeric value as b.
a < b Less than.
a > b Greater than.
a <= b Less than or equal to.
a >= b Greater than or equal to.
//= %= ^= Like the operators += -= *= /= , sets a variable to some operation of itself with an expression. Can't be chained.

Note: The 12 comparison operators (> < >> << >= <= >>= <<= == <> != !<>) can be chained like in Python, CoffeeScript, and Perl. When the above ten comparison operators are followed by another comparison operator, it will treat the expression as a compound operator. For example, a<b<c will be treated like a<b & b<c, and a>=b == c != d will be treated like a>=b & b==c & c!=d (tho it will make sure if a, b, c, or d are function calls, that they will only be executed only as many times as it was written [once each for this example].)

Unary operators defined for numbers:
- Negative. Returns the negative of a number.
++ Same as +=1.
-- Same as -=1.
! Factorial.

A string is, conceptually, a list of characters.

Lima is designed to operate with any characters, from any encoding. Unlike Python, Lima does not choose Unicode as the be-all end-all standard to convert everything to and from. Unicode, while being a giant leap forward in character encoding, has its own pitfalls and downsides. And unlike Ruby, Lima does not force all strings to carry around their encoding around with them. Instead, Lima does a combination of both: it uses its own abstract character encoding derived from Unicode, but extended to allow representations of characters from any other encoding inside the same string.

Strings are abstract entities containing "characters" (aka graphemes) that are conceptually encoding independent. In practice, like anything else in Lima, the internals will likely choose different encodings for different situations, using the advantages of different encoding when it makes sense. See the description of limaEnc8 (in the Predefined Variables: Character Encodings section) for description of how this abstract idea is implemented as a real encoding.

Lima defines a character using the encodings the system knows how to operate between. Two characters Character-A from Encoding-A is defined to be the same as Character-B from another Encoding-B if and only if the configured encodings transcode Character A to Character B and vice versa.

''

A single quoted string literal. String literals can span multiple lines, and use whitespace indentation similar to how white-space indented blocks do - each line after the first starts one character after the beginning of the expression. Multi-line strings that start with only whitespace and a newline will have the first line stripped off. Multi-line strings that end with only a newline will have the last line stripped off.

; Same as 'This is a'@,'multi-line string.' var w = 'This is a multi-line string.' ; Same as 'this is another'@,' multi-line string' var x = 'this is another multi-line string' ; Same as 'Multi-line string'@'that starts on the'@'next line.' var y = ' Multi-line string that starts on the next line. ' var z = ' This string isn't valid because the string's contents aren't indented further than the start of the statement. ' ; Same as '""""Such'@,'lines' var a = cat[" "####"Such lines "] var b = cat[x" and then "y" and then a new line"] ; same as cat[x" and then "y" and then a "@,"new line"]

Notes:

  • Lima doesn't use escape codes. Refer to the special-character operators for how to write special characters.
  • The "grave accent" ( ` ) is an alias for the apostrophe ( ' ) when being used to surround strings
  • Characters are represented as a string of length 1 - there is no separate character construct. This also means that characters are recursive lists, in a way. Eg. 'a' == 'a'[0] == 'a'[0 0 0 0 0 0 0]
    - its characters all the way down ; )
  • There are no characters for horizontal or vertical tab, delete, bells, or other non-printable characters. Tabs should be encoded as either multiple spaces, or an extended space character (using LimaEncoding's space-exstension marker), or the text should be split up into a list of strings in the case the tab character is being used as a column or list separator. Similarly, there is no difference between a line-separator and paragraph-separator - there is only a new-line character. These concepts can still, of course, be encoded in a byte-array, but are not (and have never been) expressible as (unique) characters.

"" A double quoted string literal. The same as a single quoted string except double-quotes can be part of the string and single-quotes can't.
`` A string literal quoted with grave accents. The same as a single quoted string except grave accents can't be part of the string.
"""""" A triple quoted string literal. The same as a single quoted string except double-quotes and single-quotes can be part of the string. Also note that if a triple-quoted string ends in more than 3 triple-quotes in a row, the *last* three are the end of the triple quote. For example, """x""""" is the same thing as 'x""'.
'''''' A triple quoted string literal, pretty much the same as """.
`````` A triple quoted string literal with grave accents, pretty much the same as """.
#literalString

Pseudo-operator that creates a string literal with a quote (single, double, or triple) at the front. This is used for creating string literals with a quote at the front. Whether the quote is a double-, single-, or triple- quote depends on what quotes are used to create the string literal. The number of hash marks indicate how many of the quote to write.

#"what" ; same as '"what' #'what' ; same as "'what" #"""what""" ; same as '"""what' ###"what" ; also same thing as '"""what'

Note that this only has meaning when creating string literals. This isn't an operator and has no meaning when applied to variables.

If a # precedes or follows a quote (of any kind) and is next to other operator-characters, they will be intepreted as part of the string literal and not as part of the operator.

##'hi'##@ ; Interpreted as "''hi''\n" a+#'hi'

stringLiteral#

Returns the string with a quote (single, double, or triple) at the end. Whether the quote is a double-, single-, or triple- quote depends on what quotes are used to create the string literal, just like the above form.

"what"# ; same as 'what"' 'what'# ; same as "what'" 'what'### ; same thing as "what'''"

stringLiteral#stringLiteral

Returns a string with a quote in the middle. Whether the quote is a double-, single-, or triple- quote depends on what quotes are used to create the string literal, just like the above forms.

"what"#"for" ; same as 'what"for' 'what'###"for" ; same thing as "what'''for"

Strings have all the members regular objects (object literals) have, but they also have a couple additional methods over pure object literals, and so they are not interchangable with lists of characters. For example, "hi" is different from {'h' 'i'} in that the former has string operators and methods defined for it, while the latter doesn't.

Special-character operators

Instead of using escape sequences, Lima uses operators. The following operators exist to add special characters onto a string. This summarizes their meanings:

@string

Returns the string with a newline at the front. @"string" is like "\nstring" in C.

string@ Returns the string with a newline at the end.
string%number

Returns the string with an extended space character at the end. The width of the extended space is indicated by number. For example, ""%3 is a single space character with the width of 3 single-spaces. Corresponds to LimaEncoding's space-extension codepoint.

string![hexNumber hexNumbers]

Returns the string with a sequence of LimaEncoding characters at the end. Each character is represented by a hexNumber indicating the LimaEncoding code-point or code-points the character is made up from.

"A character: "![5B] "Some characters: "![51 A7 3E2 E6] "character made from multiple codepoints: "![B4F5FC93]

Note that the true operative operators here are ! and a macro bracket-operator.

stringLiteral![base:number numbers]

Just like the normal ![ operator, but the numbers are in base base.

"A character: "![8:2703] "Some characters: "![10: 45 67 42 39]

Additional methods available to all strings:
cat list ... Just like normal cat, but each argument is automatically converted to a string using their str member.
dgt string s Returns true if the caller object is higher order than s based on collation alone (simple order). dgt stands for "dumb greater than". This is the traditional default way strings have been compared in programming.
dlt string s Returns true if the caller object is lower order than s based on collation alone (simple order). dlt stands for "dumb less than". This is the traditional default way strings have been compared in programming.
enc encoding Encodes the string into a bits using the encoding string.encoding[encoding].

Additional members available to all strings:
str

Returns the string. In the case the string has missing indexes (like if it's a slice), it will print characters in key order (ie. you don't need to do this: aSlice.sort[k].str).
code Returns a string containing the code needed to write the string literal. E.g. for "'someString'"@,"and "#,"anotherString"#.code returns #"'someString'"#,"@,"#,"and "#,"#,"#"anotherString"#,"#").
lower Lowers the case of all the characters in the string.
upper Capitalizes all the characters in the string.
name Only defined for single characters (strings that are one character long). The name of the character.
Additional operators available to all strings:
string,string

Concatenates two strings. This is in large part used for creating string literals with special-characters.

"String"," and another" #"quote"#,", said Tom." ; literal with quotation marks "Line"@,"Another Line"@ ; two lines

a < b

Returns true if a is a lower order than b in a "natural" ordering.

Natural ordering is where characters that are not digits are compared as normal, but when digits are encountered at the same place in both strings, it parses as many of them as possible into an integer and compares the integers between the two strings. The programmer can use any collation.

a > b

Returns true if a is a higher order than b in a "natural" ordering.

a <= b

Like < but matches for equal strings as well.

a >= b

Like > but matches for equal strings as well.

a..b Returns a list of strings of the same length and less length between a through b (inclusive).
collate[{'a''b''c''d''e''f''g'}]: var x = 'b'..'f' ; x gets {'b' 'c' 'd' 'e' 'f'} var y = 'cf'..'de' ; x gets {'cf' 'cg' 'd' 'da' 'db' 'dc' 'dd' 'de'}
anInteger*aString Duplicates a string a number of times.
var x = 3*'moo' ; same as x = "moomoomoo"

Note that for any of the above comparison operators and the range operator, a collation must be defined for any of them to work.

Convention A: Macro Block Format

All of the core and standard flow control constructs can either be written using square-brackets as block delimiters:

if[ i==5: ; do something ]
or with indentation (whitespace) as block delimiters:
if i==5: ; do something

The indentation delimited style will end the statement it's used in, while the bracket delimited style can allow the statement to continue.

; This is perfectly fine: someFunction[if[ x: 5 else: 100 ] + 3] ; There is no equivalent way to do that with whitespace delimitation. This causes an error: someFunction[if x: 5 else: 100 + 3 ; Error. ] ; However this can work because while the white-space delimited if statement makes it impossible to continue the ; statement being passed in as the first argument to someFunction (or passing more arguments), someFunction ; can still continue once it closes. someFunction[if x: 5 else: 100 ] + 3

Constructs that follow this convention always have at least one parameterized block. The parameters of the block are some construct that ends in a colon :. The parameter might also start with a name. Each parameterized block must have exactly 1 space of indentation, except one that starts on the same line as the macro it is a part of. The statements inside each parameterized block must all be indented by at least 2 spaces.

; This is right: if x: doSomething[] y: doSomething2[] else: doSomethingElse[] ; This is also right, if ugly: if x: doSomething[] y: doSomething2[] else: doSomethingElse[] ; This is wrong: if x: doSomething[] ; This must be indented further than the `else:` below it. else: doSomethingElse[] ; Also wrong, because all those `doSomething` statements aren't indented past the parameters. if i==5: doSomething[] i<10: doSomething2[] else: doSomething3[] ; Also wrong. if i==5: doSomething[] i<10: doSomething2[] else: doSomething3[]

The parameters for a parameterized block can be put on multiple lines in any way statements would be allowed to. So in other words, each statement in the parameter can be over multiple lines as long as subsequent lines of the statement are indented further than the start of the statement.

; This is right, if ugly: if x + 2 > 4: doSomething[] y > 5: doSomething2[] else : doSomethingElse[] ; This is also right: fn x y z= 4: ; Fugly but works, because the 4 is indented further than the start of the statement that begins with `z`. doSomething[] a b ; Since `b` and `c` are separate statements from `a` (and each other), this is ok. c: doSomething2[] ; This is wrong: if x + 2 > 4: ; This isn't part of the statement that starts with `x`. doSomething[] ; This is also wrong: if x + 2 > 4: ; This isn't even in the if statement. doSomething[]

Some constructs might not have any parameters, but still require the colon : if they're using this convention. This is an important part of this convention because it makes it unambiguous which pieces are statements and which are parameters when dealing with the possibility of arbitrary macros (that might consume things like colons).

var f = fn: ; without requiring this colon, its wouldn't be clear whether the next line is a second parameter x: ; block or a macro x that does something completely different. ret 5+x

Some constructs using this convention have a named parameter block that just makes it clearer what that parameter set does.

var f = fn.raw match args: ; here, "match" is the name of the parameter block, and `args` is the actual parameter. ret { arg: args } run arg: ; here, "run" is the name of the parameter block, and `arg` is the actual parameter. ret 5+arg

Personally I like delimiting blocks with whitespace, but sometimes bracket delimited blocks are necessary for reasonable code construction.

; lima's alternative to C's ternary operator (?:) var x = 'Lets '.cat[if[run: 'go running' else: 'walk'] ' to the park'] ; this would be silly (but possible): var x = 'Lets '.cat[if run: 'go running' else: 'walk' ' to the park' ; note that for this to work, the value on this line must *not* be any more indented than this, otherwise it would be under the else ] ; a shorter but still silly alternative: var x = 'Lets '.cat[if run: 'go running' else: 'walk' ' to the park'] ; the value on this line must still be no more indented than this

Convention B: Blocks With Multiple First-line Parameter Blocks

Constructs using this convention can have multiple parameter blocks on the same line, but the first line must be a single statement (not more or less than one). This is mutually exclusive with Convention C and is intended to be used in conjunction with Convention A.

if[x: ret x+5 else: ret 10] var a = { x = if[yes: 3 else: 4400] // Conditionally return a value. if[cat: y='meow' dog: y='bark'] // Conditionally set a privileged member. if[cat: y:'meow' dog: y:'bark'] // Conditionally set a property. } // The following breaks the convention (and won't work) because it has multiple statements in one of // the parameter blocks: if[x: a=5 ret a else: ret 5]

Convention C: Blocks that Disallow Multiple First-line Parameter Blocks

Constructs with multiple parameters disallow multiple parameter blocks on the same line. For example, fn does not allow multiple parameter blocks on the same line in order to avoid possible ambiguity between statements from the last parameter block that could also be parameters for the next parameter block. For example, fn does not allow multiple parameter blocks on the same line. This is obviously mutually exclusive with Convention B and is intended to be used in conjunction with Convention A.

; This isn't correct for `fn`. If `fn` allowed this, cases like the following would be ambiguous: var f = fn[a: a[] b=3 c d: ret b+c] ; Is b a statement that should be run for the first parameter block, ; or is b a parameter in a new parameter block with c and d (where 3 is its default ; value)? `fn` avoids this ambiguity by simply disallowing this.

Convention D: Macro Block First-line Parsing

Constructs following this convention require that any macros that appear in the first line of a parent macro be in scope when the parent is run, and that if the macro changes between run of the parent and run of the inner macro, that the number of characters that the inner macro consumes is consistent. This convention is important for constructs like fn or if that don't necessarily run the statements inside them immediately (or ever). This is intended to be used in conjunction with Convention A and Conventions B or C.

; Even if `x` is false, `someMacro` must be in scope so that how many characters it consumes can be determined ; so the rest of the if can be properly parsed. if[x: someMacro --anotherVariable-- else: false] ; Similarly, `someMacro` here must also be in scope and be consistent. var x = fn[a: ret someMacro a][3] ; Even if `someMacro` is redefined in the function, it must be already defined before the function, ; otherwise the parser would not know what source code is part of the function. var x = fn[a: someMacro=getMacro[] ret someMacro a][3]

Convention E: Implicit Variables

There are a number of macro constructs that define implicit variables for accessing information about items in an object. These are defined to make it more convenient to write terse code. Usuaully, the names of implicit variables can be overridden with explicit names by listing alternate names before a colon (eg obj[[key value: key<value]]).

For constructs with a single relevant index, the conventional variable names are:

For constructs with a single relevant item, the conventional variable names are:

For constructs with two relevant items, the conventional variable names are:

For constructs that follow this convention, if the implicit variable would shadow or conflict with an in-scope variable, an exception should be thrown. Similarly, if the implicit variable name is overriden with a name that is already in scope, an exception should also be thrown.

Convention F: Interface Operator

A much as makes sense, the ! operator should be reserved as the interface operator. If the ! operator is overridden, there should be some other exposed way to create a type that matches the object in question.

Convention G: Ranges are always inclusive

Whenever a mechanism for creating a range is created, the range should use inclusive semantics. Eg. the range should include both the starting and ending points that you use to define the range.

The justification for this is that its easier to exclude the ends in another way, but it can be much harder to do the reverse. For example:

range1 = createRealInclusiveRange[.1 3] range1 .=rm[[v==.1]] ; Makes the range exclude the starting point. range2 = createRealExclusiveRange[.1 3] ; How would you turn range2 into an inclusive range here?

Convention H: Log levels

It is encouraged that the following log levels be used with their defined meanings:

  1. 'debug' - Logs used for debugging.
  2. 'info' - Logs that give supplementary information about what's happening in the program.
  3. 'warn' - Logs that warn about potentially harmful things.
  4. 'error' - Logs that warn about error events.
  5. 'severe' - Logs that warn about severe error events.

Convention I: Exception Conventions

Exceptions in Lima should almost always have a descriptive, human-readable error message (str property) that contains relevant information about the exception. To facilitate programmatic exception handling, an error should also have a consistent 'name' property. In addition, any variable information included in the str message should be accessible individually as properties on the exception object.

These variables are are part of core Lima.
Any value except nil. A variable of this type can take on a value of any type except nil.
Variables of this type hold a lima string. Has the following members:
chars The list of every canonical character the system knows about. Characters that are equivalent are not both listed, one is chosen arbitrarily.
encodings The list of supported string encodings.

string.encodings has the following additional method:

add name chars Adds an encoding named name. chars is a list of all the characters that encoding can encode, in LimaEncoding (string) form.

encoding objects

encoding objects have the following members:

name Unique canonical name for the encoding. Basic (non-parameterized) encoding names can contain any character except brackets ('[' and ']'), and are case-insensitive ("UTF-8" is the same as "utf-8"). The names of parameterized encodings pass LiON formatted parameters in brackets. For example, a little endian utf-16 string with a tab-width of 5 would have the name 'utf-16[5 "little"]'.
chars A list of all the characters that encoding can encode, in LimaEncoding (string) form.
aliases List of aliases for this encoding. Does not include its canonical name. Adding aliases to this list will allow this encoding to be accessible from string.encodings at that key. Adding an alias whose name is already in string.encodings causes an exception.
transcoders

List of transcoders keyed by the canonical name of the encoding transcoded to. Only lists original transcoders, does not list deduced (chained) transcoders.

You can add or change transcoders by modifying the list. Adding a transcoder allows it to be used (directly or chained) by other related encodings. Adding a transcoder can also change the chars list (of the encoding or others) to minimize the total number of characters in the system. An exception will be thrown if a key of an unknown encoding is set.

If an added transcoder conflicts with (contradicts) another added transcoder, and exception is thrown. If any character can be transcoded to a different character, by transcoding from LimaEncoding through any chain of transcoders, then some transcoder conflicts with one or more other transcoders. This means that every character in an encoding's chars list must be a unique character.

The transcoder functions that can be added may take two parameters:

  1. A bits object containing the encoded string to transcode.
  2. An object containing the encoding's param member
The transcorder functions must return bits objects.

params

This member will only exist if the encoding has been passed parameters.

encoding objects have the following methods:

encodingName

Like a normal bracket operator for accessing object members, but recognizes encoding parmaeters. Encoding parameters are described in the section for the name member of encoding objects. These additional arguments are added into the params member of the returned encoding object. For example, string.encodings['someEncoding[2 "hi" {test="testing"}]'], would return an encoding object with params set to {2 'hi' {test='testing'}}.

fits character Checks if the calling encoding can encode the passed character.
fits encoding Checks if the calling encoding can always be converted into the passed encoding - if that encoding "fits into" the other one (if all characters can be converted from one to the other). encodingA.fits[encodingB] is the same as encodingA.chars.map[encodingB.fits[v]].join[v&v2]
enc theString

Encodes theString into binary. Returns a bits object.

If a character in the string can't be encoded by this encoding, it should call the function contained in the atr.encodingFail. The enc method should *never* throw an exception when a character can't be encoded, although encodingFail can decide to throw an exception.

dec bitsObject

Decodes bitsObject into a string.

If a character in the string can't be encoded by this encoding, it should call the function contained in the attribute decodingFail.

Represents a variable type. Things like templates in C++ or generics in Java would instead be made using variables of this type.
Type methods:
type.set Creates a type from a set of values. Variables defined as that type can only take on those values.
type.set[0 1 2 3 4 5 6 7 8 9] digits ; a type 'digits' that represents the values 0 through 9
type.cond Creates a type from a condition. Has the implicitly-declared variable v representing the value to be tested (for whether it's included in the type).
type.cond[v>0] ; all values that are greater than 0 (or otherwise have that opration defined to be true)
varWord Represents a named variable. This can be used to match named variables, including undeclared variables, in operator dispatch. See "Overloading the dot operator" for an example. It isn't generally useful as a variable type outside the area of operator dispatch.
probability

A probability map type. Provides a way to query information about probabilities.

probability has the following methods:
mass probabilityMassFunction Returns a probability object derived from the function probabilityMassFunction. probabilityMassFunction must be inversible function where the parameter represents the value, and the result represents the probability at that discrete value.
var p = probability.mass[fn int[x] y: if 0<x<=5: ret 1/20 5<x<=10: ret 3/20 ]]
density probabilityDensityFunction Returns a probability object derived from the function probabilityDensityFunction. probabilityMassFunction must be inversible function where the parameter represents the value, and the result represents the probability density at that value.
var p = probability.density[fn x: ret e^(-x^2/2)/(2*pi)^.5 ]]

The above probabilityMassFunction and probabilityMassFunction parameters must be inversible functions where the parameter represents the value, and the result represents the probability at that discrete value.

probability objects

probability objects (ones returned by probability.mass or probability.density) have the following methods:
value The normal bracket operator. Accesses the probability of a discrete value.
slicingStatment The normal object-slicing operator. Can accesses values and their probabilities that match certain conditions. Using this operator on a probabiliy defined by a density function, this will always return 0. If Lima ever introduces infintesimal numbers, this will change to returning an infintesimal.
sum slicingStatment

Returns the sum of probabilities for the given slice. This is like the double-bracket operator, but can't use the implicitly-declared variable v.

This is shorthand for pmap[[slicingStatement]].join[0 v+v2].

percentile percentileNumber Returns the value at a given percentileNumber through the distribution. Percentile is given as a value from 0 to 1. Throws an exception if the value-set is not ordered.
pmap.percentile[0] ; minimum value pmap.percentile[1] ; maximum value pmap.percentile[.6] ; 60th percentile

Parameterized and compound types are functions that return a type.

!

Type that can only take a list (an object with consecutive integer keys starting from key 0).

list! x = {4 5 6}

For an object to match the list! type, it must also still have all the default operators, methods, and members defined (they can be redefined but not removed).

[type]

Returns a list type that can contain members of the type given in the brackets.

list[int] x = {4 5 6} list[int$string] = {5 6 'hi'} list[list[int]] y = {{1 2} {5 9} {100 101}}
Note that when a type is passed into list or ref via the , it does not need to be appended with the reference access operator (~).

fn! See the section on functions for details.
ref! See the section on ref for details.
file! See the section on file for details.
dir! See the section on dir for details.
standardLibrary This object represents the entire standard library of Lima. This automatically mixd in every module as private, but if it is explicitly mixd, you can alias or exclude certain parts of the standard library.
; includes the whole standard library except logger, and aliases depend to buildDependency ; (which frees that name up for something else) private mix[standardLibrary:[! logger depend:buildDependency]

An object for creating continuations that can be ed to. When copied, returns the current continuation (the line after the line executing the copy of contin). This is similar to a jump-label in C.

Returns a type that can only hold a contination object.

See the section on jump for details.

Object containing methods related to references.

Note that immutable references can still be used to mutate the underlying variable, as long as that pointed-to variable is mutable.

var x = 5 mut p ~> x logger.i[p] ; prints 5 p = 6 ; x is now 6 var y = 90 p ~> y logger.i[p] ; prints 90

More examples:

f = fn int x: ret x+1 g = fn mut int x: x++ h = fn mut ref.int x: x++ main = fn: mut int i = 10 mut ref.int? p1 mut ref.ref.int p2 p1~ ; exception (p1 doesn't have a value yet) p2 ~> p1 ; type mismatch exception (p2 can't point to an int) p2 ~> nil ; type mismatch exception (p2 can't point to nil) p2 ~> p1~ logger.i[p2] ; undefined variable exception p1 ~> nil logger.i[p2] ; type mismatch exception (p2 can't point to a reference containing nil) p1 ~> i f[i] ; returns 11 f[p1] ; returns 11 f[p2] ; returns 11 g[i] ; i is now 11 g[p1] ; i is now 12 g[p2] ; i is now 13 h[i] ; type mismatch exception (h doesn't take an int value) h[p1] ; type mismatch exception (h doesn't take an int value) h[p2] ; type mismatch exception (h doesn't take an int value) h[p1~] ; i is now 14 h[p2~] ; type mismatch exception (h doesn't take a ref.ref.int value) h[p2~~] ; i is now 15

Has the following operators:

Returns a reference type that can reference anything except nil.
? Returns a ref type that can also point to nil.
ref? ; reference to anything, including nil.
Returns a value that, when copied, copies to a reference of the input value. This can be useful for returning references from a function, creating lists of references, and can be important in cases where function arguments are mutated and may be either references or non-references.
var a = 5 var b = 6 mut x ~> a ; x points to a var y = ref[b] ; y points to b mut z = {ref[a] ref[b]} ; z gets a list of references var func = fn mut[one] two three: one ~> b two = ref[b] ret ref[three] ; returns a reference, same as three~ ; here, result gets a reference to a, ; x gets pointed to b instead of a, and ; z[0] doesn't change, but a gets pointed to b var result = func[x z[0] a]

Returns a reference type that can reference the passed type.

ref[[int]] x ; reference to an integer ref[[int$real]] ; reference to an int or real

Has the following methods:

weak value Just like ref[value], but is a weak reference. A variable will implicitly get a strong-reference if it isn't declared as a weak reference. All weak references are nilable, so assigning a weak reference to a ref! variable will make your type-checker complain.
var x = 5 var p ~> x ; strong reference ref p2 ~> x ; another strong reference var p3 = ref.weak[x] ; weak reference var p4 ~> ref.weak[x] ; p4 gets a strong reference here
When an object that a weak-reference is destroyed, it is automatically set to nil

Reference objects

Reference objects pass all accesses through to the object it points to, except for the following operators:

~

Reference access operator. An operator that accesses a reference itself, as opposed to as what it points to (what usually happens). You would use this to return a reference from a function, put a reference into an object immediate, or point one reference to another.

X = fn: int r=5 ref! x x ~> r ; x points to r var A = {reference: x~} ; an object that contains a reference logger.i[A.reference] ; outputs 5 ret A.reference~ ; returns the reference to r

The more ~'s in the operator, the farther down in to the reference chain it accesses.

x~~ would access the reference x points to (like *ptr in C). x~~~ would mean to use the reference that the reference x points to, points to (like **ptr in C), etc. More examples:
int: r=5 t=36 mut ref!: x y x ~> r ; x points to r y ~> x~ ; y points to x y ~~> t ; x is repointed to t var A = {x~ y~ y~~} ; A is a list that contains the three references (x, y, and x again [what y points to]) ret A[1]~~ ; returns the reference to t (y holds a reference to a reference to t)

This is also used to indicate that you want to use an or macro rather than call it on the proceding code. For example,

{int~ real~ var~} ; building an object that contains type attributes function[if~ df~] ; passing the core macros if and df into a method

Variables can be set to references, but as usual it creates a copy.

var[x=5 y=6] mut ptr ~> x var a = ptr~ ; a now also points to x, but it is a copy ptr ~> y ; ptr points to y, but a still points to x logger.i['ptr: 'ptr', and a: 'a] ; prints 'ptr: 6, y: 5'

Using the ~ operator on an object that isn't a reference, , or macro returns the object itself (ie a no-op) except that it no longer has the ~ operator. This means that while 5~ is legal, 5~~ will throw an exception.

~ Bottom-up reference access operator. Accesses the reference based on its level of indirection from the value ultimately being pointed to. For example:
var x = 5 var p1 ~> x var p2 ~> p var p3 ~> p2 p3~1 ; refers to p1 p3~2 ; refers to p2 p3~3 ; refers to p3 p2~1 ; refers to p1 p2~2 ; refers to p2
~>

Sets the reference that a ref points to. The number of tildes (~'s) tell you how "deep" the assigning goes.

mut ref.ref.int a ref.int[b c] int[x y] b ~> x ; b now points to x a ~> b ; a now points to x (since b points to x) a ~~> y ; this sets whatever a points to (i.e. b), to now point to y c ~> b~ ; c now points to what b points to (i.e. y).

Note that multiple tildes in this operator works the same way as the unary ~ operator.

a ~~> b ; means the same as: a~ ~> b a ~~~> b ; means the same as: a~ ~~> b ; and a~~ ~> b
Also note that x~ = y~ is the same as x ~> y~~ if x and y are both normal references.

~>> Just like ~> but sets as a weak reference.

See also: weakRef.

meta Object with methods for reflection and meta programming. This follows the design principle of mirrors.
Methods:
object Returns an object containing meta data about the passed object. The returned object has the following properties:
  • type - The type this value has been constrained to.
  • mut - True if the object is mutable, false otherwise. If this meta-property is false, none of the meta properties can be changed either.
  • name - The name of the variable or key the object is stored under (eg its variable name, property name, map key, array index, etc).
  • interfaces - A map of the interfaces the object implements. Each key is a reference to one of the objects this object implements the interface of, and each value is a map from original property name to aliased property name.
  • operators - An map from binary operator names to information about that operator. Each value is an object containing the properties:
    • dispatch - A list of objects representing the sub-functions that make up fn's multiple dispatch list. Each element contains the properties:
      • parameters - A list where each element is an object with the following properties:
        • name
        • type
        • default
      • fn - The function to be called on for matching parameters.
    • order - The 'order' number used to determine operator precedence.
    • backward - If true, the operator is right-to-left associative (like =). If false, the operator is left-to-right associative (the usual case for most operators).
    • chains - If true, the operator can chain. If false, the operator can't chain.
    Note that operators has a macro bracket operator that can take in raw operator tokens in addition to string representations of the operator.
  • preOperators - An object representing prefix operators. Has the same form as operators except won't contain the order, backward, or chains properties.
  • postOperators - An object representing postfix operators. Has the same form as preOperators.
  • macro - An object containing the properties:
    • macroAst - The function used to generate a macro's abstract syntax tree.
    • limaAst - The function used to transform the macro's AST into a lima abstract syntax tree.
  • inherit - The function used to inherit this value when mixing it into another object. In most core and standard objects, this just copies each property value to a particular property name.
  • privileged - A map of public privileged members. Each key is the name, and each value is the value.
  • properties - A map of normal non-privileged members. Each key is the key-value, and each value is the value-value.
  • destructors - A map from constructor to destructor in the order each was (first) called. This list will be called in order when an object becomes inaccessible to the rest of the program.
  • history[time] - Returns information about an object at a particular time in the program. The time can be in the future. Returns an object with the following members:
    • val - A new object (ie a copy) that represents the state of the object at the passed time.
    • atr - The context attributes that were set the when the object was mutated most recently relative to the passed time.
    If the time is before the variable was created, it throws an exception. The 'history' property has the API of a list.
    var t1 = time.before: mut x = 5 var t2 = time.after: x = 8 var oldx = meta[x].history[t1].val ; returns 5
  • detachmentTime - The time the object will become inaccessible (usually in the future).
Note: accessing an operator function is a good way to work around objects with operator conflicts. You can specify which operator function to use. Eg: meta[obj1].operator[+][obj1 obj2]
Methods:
set Returns a random value in the set using a random number generator chosen by default criteria. Returns the same distribution of values the following generator would return:
rand.generator[fn properties: var p = properties[10'000] ; indicates that the probability maps should be consistent for data-set ranging from 1 byte to 10 thousand bytes long ret p.entropy[[k>.99]] > .99 ; hard to guess values & p.mean[[k<.5]]-p.mean[[k>=.5]] < .05 ; relatively symmetric averages & p.chiSquare[[0.8<k<1.2]] > .95 ; reasonable normalized chiSquare values & properties.cycleLen > 10^6 ; long cycle length & properties.seedable ]
generator accept

Returns a random-number generator with specific properties described by the arguments. seedable is a boolean value describing whether the random number generator needs to be seedable (default is true). accept is a function that should return true if a random-number generator's attributes meets it's requirements. accept takes one parameter of the same form that rand.addGenerator takes.

The random-number generator that rand returns has the following method:

set Returns a random value from the list set. set may not be an infinite list.
Methods:
seed The seed used to seed the random number generator. This member is mutable. If rand's bracket operator is used before rand.seed is set, it will be seeded automatically by the selected random number generator. Setting the random seed really should only be used for debugging purposes. This member only exists for random number generators that are reproducible.

rand[1..800] ; returns a random value between 1 and 800. ; does the same thing, but ensures that the random number generator used ; doesn't restart its cycle until after 9 million numbers have been generated, ; and at least 99.5% of the time has a normalized entropy of greater than .99 ; for generated data with a length less than or equal to 1000 bytes. rand.generator[fn properties: if properties[1000].entropy[[k>0.99]] > .995 & properties.cycleLen[[k>10^9]] == 1: ret true else ret false ][1..800]
addGenerator name properties Adds a random-number generator named name to the list of available generators. properties describes the properties of the generator and has the following members and operators:
Members:
cycleLen A probability map for how many numbers it generates before its pattern repeats
seedable True if the random-number generator is seedable, false if not.
Operators:
sequenceLength Returns an object containing attributes of a particular random number generator. Each member is a probability map, mapping a particular measurement to its probability, that is consistent for all byte-sequences generated by the random-number generator with lengths less than or equal to sequenceLength. The members are:
  • cycleLen - how many numbers it generates before its pattern starts over again
  • entropy in entropy per bit (a value between 0 and 1) - describes how easy it is to guess any given generated value.
  • chiSquare - the normalized Chi-square value (Chi-square per degree of freedom)
  • mean - the arithmetic mean of the bits generated normalized by dividing by the number of bits (so that the value is between 0 and 1).
  • monteCarlo - the arithmetic mean normalized by dividing by the number of bits .
  • serialCorrelation - the serial correlation coefficient (a value between 0 and 1). Roughly describes the extent to which each value depends on the previous value.
  • kComplexity - the Kolmogorov complexity divided by the number of bits (also a value between 0 and 1). Roughly describes how compressible generated values are.
See http://www.fourmilab.ch/random/ for more info on randomness tests.
Members:
generators Pseudo member that returns a list of available random number generators, keyed by name. The list can't be directly modified (its modified through addGenerator). The values associated with those keys are objects - containing properties of the random-number generator - of the same form that rand.addGenerator takes as an argument.

indicates that the programmer doesn't care which value is chosen - ie. it is arbitrary as far as the programmer is concerned. This might be used for example when you want to obtain one item out of a list being used as a set, but don't care which one it is. You could ask for the first of the set, but if you don't need that one specifically, you should ask for an arbitrary one: arb[aSet].

Similar to rand, arb takes a list of values that may be returned. Like the concept of a don't-care in digital logic design, the compiler will attempt to use values in the list that optimize the program's speed or memory usage.

Methods:
Returns an arbitrary value that is in the .

can allow you to do things that almost no other programming language can: performance-based behavior. For example, you could choose between three different algorithms for text differencing:

private: algorithms = { minimal: fn[ ... ], ; produces the smallest output light: fn[ ... ], ; uses the least memory quick: fn[ ... ] ; runs the fastest } textDiff = arb[algorithms]
And in your main program:
optimize[5*cputime + 2*memory]: var textDiff = load['textDiff'].textDiff var diff = textDiff[file["someFile.txt"]] logger.i[diff] ; output on the console
Each of those algorithms would produce different output (otherwise there would be no need to use ), but the appropriate one would be used for the system its running on based on the optimize statment.

chan

A chan object is a construct that can facilitate communication between processes. They are similar to channels in Go and in Stackless Python. They're also related to the concept of a pipe (e.g. in unix).

A channel consists of an input list and and output list. Reading from the output list will block until the value asked for is known. The copy operator of chan creates a new empty channel, so all you need to do to create a new channel is var newChannel = chan.

mut c = chan thread: list.sort[] c.out.=ins["done"] var a = c.in[0] ; waits for any input on the channel to arrive, and a gets "done" when it does

Channels can be used to communicate between thread blocks, but channels are also used as the conceptual basis for network communication - because you usually don't know exactly when information might arrive on a socket.

chan objects

Objects created by copying chan have the following members:

in Conceptually, the list of all items that have been and will be recieved on the channel. Copying the in member creates a new object containing the same list of items and will receive all the same items that the original in member gets. This ensures that multiple receivers are guaranteed to see all items sent, even if the original in member removes items (e.g. if it uses it is a queue, cutting items from the front).
out The list of items that have been sent on the channel. Sending on the channel is done by appending to this list. Copying the out member creates a new object containing the same list of items, but when modified only shows modifications to the copy, not the original. This allows multiple senders to send items and keep track of just the items they sent. This object can only be appended to - elements, once written, can't be changed or removed. Also, creating values at keys that are not the next position in the list will throw an exception.

Objects created by copying chan have the following pseudo-members:

close Closes the channel - at this point the length of the in member is known.
unmake On unmake, the channel's close method is automatically called.

Objects created by copying chan have the following methods:

& otherChannel Combines two channels. Sending on a combined channel will send on both (multicast), and receiving from a combined channel will receive the first message that comes in on either channel (multiplex).
var channels = c1 & c2 channels.in[0] ; gets the first item that comes in on either of the channels
unixtime An object that has methods for retriving a unix timestamp.
Pseudo-members:
after: statements A macro that returns an integer unix timestamp representing the time directly after the statements have run.
Timestamps capturing is still allowed on machines that don't have the actual date or time availble to it, as long as only time-differences (ie durations of seconds) are used.
In Lima, there is no "now". Because statements can be reordered to optimize performance, there would be no way to guarantee that a time is captured both after its preceding statements and before its following statements without destroying lima's ability to reorder statements optimally. So instead, Lima allows you to choose to either bind a time before some given statements or before them, but not both. If you need both, you need to create two separate time objects.
; the following to sets of statements can be run completely in parallel ; which means that ta1 may be the exact same time as tb1 ; and also means that tb2 might even be earlier than ta2 (if 'B.txt' takes less time to download and output) var ta1 = unixtime.before: var ta2 = unixtime.after: var x = downloadFile['A.txt'] outputFile['some/path/A.txt', x] logger.i['File A took: '(ta2-ta1)' seconds to download'] var tb1 = unixtime.before: var tb2 = unixtime.after: var y = downloadFile['B.txt'] outputFile['some/path/b.txt', y] logger.i['File A took: '(tb2-tb1)' seconds to download'] ; the following statement guarantees that the time is as close the console output as possible var t = unixtime.before: logger.i['The unix time is: 't] ; the following statement throws an exception because t isn't declared until after the logger.i statement runs var t = unixtime.after: logger.i['The time will be... 't]
before: statements A macro that returns an integer unix timestamp representing the time directly before the statements have run.
resolution A duration object representing the smallest amount of time (in seconds) that can be distinguished by the machine (often this is related to the time between between "ticks" of the cpu).
accuracy A probability map describing how accurate the clock is likely to be at the moment this member is accessed. The object maps the likely difference, in seconds, to its probability (value from 0 to 1).
; if there's at least a 90% likelyhood the absolute deviation is less than half a second if time.accuracy[[-.5<k.5]] < .9: resyncClock[] ; made up function that might resync the clock from the internet

The process object is only available in the entry point module and any information from it needed by other functions or modules must be passed to them.

Members:
name Name of the command used to invoke the program. This is the value you get in main's argv[0] in C.
args A list of the command-line arguments to the program.
logger.i['Sum: ' process.args[0]+process.args[1]]
signals A list of signals sent to the program.
change[process.signals]: logger.i["Got a new signal: "process.signals.sort[-k][0] ''@]
stack Contains a list of stackline objects representing the call stack, in deepest-first order. Each stackline object has the properties:
file The name of the source file that contains the stackline.
function The name of the function file that contains the stackline.
line The line number of that stackline in its original source file.
column Character-column at which the current statement running at that stackline started in its original source file. If the currently running line has more than one statement, this could be used to identify which statement by its starting position.
pid Process ID of the program.
start Time the program started running.
end Time the program will end (used for describing things that should last til the end of the process).
vendor Name of the maker of the compiler or interpreter.
version Version of the compiler or interpreter
environ Holds a list of the environment variables given to the process. process.environ members can be added and modified, but this will not affect the rest of the system.
moduleCache Holds a map to the modules that have been loaded and run. Removing the value at keys in this object will cause the next module import (via use or load) to re-execute the module and store a different instance in the cache.
rmSigintHandler[] Removes the default SIGINT handler. The default SIGINT handler terminates the process.
rmSighupHandler[] Removes the default SIGHUP handler. The default SIGHUP handler terminates the process.
Methods:
executablePath args input cwd env

Starts a process, whose path or name are held in executablePath, with the command-line arguments args, held in a list of strings. input (optional) is the stdin input piped into the process. cwd (optional) sets the current working-directory for the new process, the default is the directory it lives in. env (also optional) sets the environment variables for the new process.

Returns a process-object (which implements the interface of future):

Process-objects have the following properties:
pid The processes id.
wait: A macro that waits until the process is complete before running the passed statements. See the section on future for more info.
out A string object representing the final state of the process's stdout.
err A string object representing the final state of the process's stderr.
code The exit code of the process.
attached True if the process will die if the parent dies, false if it is detached and will continue living even after its parent dies.
state Returns
  • 'active' if the process is still running,
  • 'done' if the process has completed, or
  • 'dead' if the process has been killed.
signal[signalName] Sends a signal to the process. The signal can be one of the following
SIGTERM Not supported on Windows
SIGTERM and SIGINT have default handlers on non-Windows platforms that resets the terminal mode before exiting with code 128 + signal number. This default behaviour can be removed by calling process.rmSigtermHandler[] and process.rmSigintHandler[].
SIGINT Can usually be generated with CTRL+C (that may be configurable).
SIGPIPE
SIGHUP Generated on Windows when the console window is closed, and on other platforms under various similar conditions, see signal(7). The signal can be handled, but the process will be unconditionally terminated by Windows about 10 seconds later. On non-Windows platforms, the default behaviour of SIGHUP is to terminate the process, but this default behaviour can be removed by calling process.rmSighupHandler[].
SIGBREAK On Windows, delivered when CTRL+BREAK is pressed. On non-Windows platforms there is no way to send or generate it.
SIGWINCH Delivered when the console has been resized. On Windows, this will only happen on write to the console when the cursor is being moved, or when a readable tty is used in raw mode.
SIGKILL Not a handlable event (will not create an event in the signals list), it will unconditionally terminate the process on all platforms.
SIGSTOP Is not handleable (will not create an event in the signals list).
kill Kills the process immediately. Calls with wait will throw an exception.

system Methods for making system calls. The encoding used for this is the same as con.enc. The behavior of this object can be overridden using the matr.systemObject module attribute.
Members:
name The name of the operating system.
version The release version of the operating system.
machine The machine-name/hosname of the OS.
platform The platform of the OS (ie win32, etc).
arch The architecture of the OS (ie x64, etc).
tempdir Returns a dir object representing the system's default directgory for temporary files.
endian The endianness of the cpu. Either "BE" or "LE".
uptime Returns a dur object representing the amount of time the system has been on for.
mem An object containing the following members
  • free - number of free bytes of memory in the system
  • total - number of total bytes of memory in the system
hosts An object where each key is a hostname defined by the OS (/etc/hosts and the like), and each value is an adr! representing the address given to that hostname.
cpus A list of objects with info about each available cpu/core. Each object has the following structure:
  • model - the name of the cpu
  • speed - the speed of the core in MHz
  • times - contains an object of the time in milliseconds the core spent in various categories:
    • user
    • nice
    • idle
    • irq
environ Holds a list of the environment variables set for the operating system (or current user). When system.osEnviron members are be added or modified, this will make changes to the whole system as well as automatically change the corresponding value in process.environ.
processes Returns the list of process objects for all the processes the user is allowed to see on the system.
Methods:
process pid Returns a process object representing the process with the given pid.
exit code A function that exits the program with the passed exit code. This behavior can be overridden using the matr.exitFn module attribute.
friends path ... Declares modules that can access the private members of objects defined within the current file. Each passed-in path can either be a module path of the same type passed into use, or can be a directory path, which would mean that all modules within that path are allowed to access private members from the current module. This function can only be called once per module.
friends['./tests' './src/some/module']
strongError errorObject A function that creates a "strong" error, which is an error that the compiler will warn about if it can prove that it could happen with the allowed inputs to a program. The compiler will only error when this is detected if the package.lima's errorChecking is set to "strong". This uses the attribute atr.strongError.
Denotes that external inputs to the program within a statement are ready immediately (ie at compile time), and so can be grabbed as soon as the program knows how to grab them. Usually this means that if the program knows how to grab the input at compile-time, it'll do it at compile time, but if not, it obviously can't grab it until runtime, and the doesn't have any effect (ie input is grabbed when convenient - the default). However, if the program can determine that some kind of input caching can be done (for example, if the inputs are fairly regular, tho not 100% deterministic), it may grab the input during runtime once, and then have it stored for later executions, essentially making it available at compile-time for those subsequent executions.
Uses the atr.ready attribute.
outReady
Denotes that external outputs from the program within a statement are ready immediately (ie at compile time). (ie that the external entity being outputted to is not mutated in a way this program cares about by that output). Labeling something outReady allows the optimizer to run outputs at compile-time if their inputs are also ready.
For example, in a CRUD API setup, read calls require the program to send some data to the external API, but those outputs wouldn't normally mutate the external server, but only provide the information that external service needs to send the appropriate data to your program.
Uses the atr.outReady attribute.
mut

Makes a variable on able to be mutated more than once.

Variables not declared with mut can only be mutated once (eg by being initialized), after which its value is locked. This includes all the members (including members declared mutable) and properties of that value. If the value the variable is initialized with contains any ref variables, the objects they reference can be changed as normal but they can't be pointed to different objects.

All members of a mutable object are mutable by default. Use const to make members of a mutable variable immutable.

const

Used to mark members of an object constant. This is generally used to declare immutable members of an object contained in a mutable variable.

shadow

Allows a newly declared variable to shadow a variable in an upper scope - meaning that the variable declared with shadow can have the same name as a variable in an upper scope. In a circumstance like that, the variable in the upper scope becomes in accessible to the scope the shadowing variable is defined in. However before the shadowing variable is declared, the upper-scope variable is still accessible. This is intended for variables like ret that are an important part of the construct they're defined in. This is not intended to be used in normal code.

hoist

Makes a statement run at the top of the scope. Hoisted statements are executed in the order they're written in, but before non-hoisted statements. This allows you to do things like define methods and variables below where they're used.

;logger.i[z] ; Would throw an exception because z isn't hoisted logger.i[y] ; prints 5 z = f[] logger.i[z] ; prints 42 hoist x = 5 ; Single-expression form hoist: ; Multi-expression form y ~> x f = fn: ret 42
:  conditions  : [ ]

if is lima's if construct. The conditions to an if statement are defined in a (explained in more detail in the section on functions) Any statement can be executed conditionally using if (including variable declarations). This is the only function-like construct allowed in a .

if x != 'something': doSomethingWith[x] if [ isRed: logger.i['Yes its red'] isGreen: logger.i['Its green instead!'] isBlue: logger.i["We've got a blue"] else: logger.i['Its some other hue'] ]

if returns the value of the last statment executed, and so can be used like the tertiary operator ?: in C (and other languages):

var x = if a>b: 'A is bigger' else: 'B is bigger' ; x gets a string that says which one is bigger var y = 500 + if[one:1 two:2 three:3 else:0] * 20 ; y gets a number

Note that, like javascript, if does not define a new scope. So variables can, for example, be conditionally defined.

: else: [ : else: ]

A more general loop construct. First checks the . If it is true then runs the and , then starts over.

The can be omitted. If both and are omitted, the loop is infinite (it will continue to loop until some code s out of it). The optional else block runs if the while condition isn't met even once.

and are available in this form of loop as well. exits the loop without executing . runs then checks the condition before starting the next iteration.

and create new scopes, so variables declared inside won't persist after the loop, and variables inside (and for , the value, key, and count variables) can safely be assumed to be used in asynchronous callbacks with the value they held when the callback closure was created.

throw object

Throws an exception object. Throwing an exception will interrupt the execution of any following code - execution will continue from where the exception is caught. If the exception is not caught anywhere up the stack, the unhandled-exception handler is called. If there is no unhandled-exception handler, or if an unhandled exception occurs in the unhandled-exception handler, the error is considered fatal, and the program prints the exception and immediately exits.

Upon being thrown,

  • A stack trace will be added to the object as the member trace (overwriting it if it exists)
  • The str member of the object will be set to obj.oldStr.cat[@''+obj.trace]
  • The member causes of an exception is overwritten as an empty object on being thrown. causes is used to store a list of related exceptions.

Example of an exception:

Error in the hood:
main /moose/whatever.lima:44:10
obj.y /moose/candy.lima:44:10
Caught:
func /moose/modules/x.lima:44:10
doSomething /moose/whatever.lima:44:10
a.b.c /moose/whatever.lima:44:10
Caused by:
0. Uncalled For Exception
   main /moose/whatever.lima:44:10
   obj.y /moose/candy.lima:44:10
   Caught:
   func /moose/modules/x.lima:44:10
   doSomething /moose/whatever.lima:44:10
   a.b.c /moose/whatever.lima:44:10
1. Shouting Failure
   main /moose/whatever.lima:44:10
   obj.y /moose/candy.lima:44:10
   Caught:
   func /moose/modules/x.lima:44:10
   doSomething /moose/whatever.lima:44:10
   a.b.c /moose/whatever.lima:44:10   
   Caused by:
   0. Bug In Your Junk
      main /moose/whatever.lima:44:10
      obj.y /moose/candy.lima:44:10
      Caught:
      func /moose/modules/x.lima:44:10
      doSomething /moose/whatever.lima:44:10
      a.b.c /moose/whatever.lima:44:10
							

: primaryStatements ; exception: handlingStatements ; finally: finallyStatements [ ]

Lima's try-catch mechanism. Catches and handles thrown exceptions.

Runs primaryStatements, and if any exception is thrown in those statements, catches and allows handling of the exception in the try-exception block. The handlingStatements in that block are then run (the exception is referenced by the name exception). The always runs after the primaryStatements and handlingStatements are complete, even if the primaryStatements or handlingStatements throw an exception, return from the function, or jump to a continuation. In those cases, the value to return, throw, or jump to is computed before the code enters the finally block, and the finally block finishes before the value to return, throw, or jump to is actually returned, thrown or jumped to.

The and are both optional, but either one or the other must exist. Note that, like if, the try block doesn't define a new scope, so variables declared inside it persist beyond the try block.

When an exception is thrown from the try-exception block that is different from the one that triggered the try-exception block, the exception that triggered the try-exception block is prepended to the causes list of the newly thrown exception. Multiple causes can be attached to an exception when a try block nested inside a try-exception block throws an exception, in which case the outermost cause appears first in the list.

asyncTry: primaryStatements ; exception: handlingStatements asyncTry [ ]

Asynchronous try-catch. Catches and handles exceptions thrown from threads (does not catch synchronous exceptions). This is similar to domains in node.js.

Runs primaryStatements, and if any exception is thrown from threads in those statements, it runs the handlingStatements (the exception is referenced by the given name exception).

This construct uses the uncaughtExceptionHandler attribute, and so has the same familiar nesting behavior as normal try. Like try, asyncTry'a main block doesn't define a new scope, so variables declared inside it persist beyond the asyncTry block. However, asyncTry's exception block *does* define a new scope.

When an exception is thrown from an asyncTry block, the exception that triggered the try block is appended to the causes list of the newly thrown exception.

rawthread: rawthread [ ]

A thread. Runs the asynchronously. This is a similar concept to threads in a language like C or Java, or setTimeout in a language like Javascript. Although a thread can't take parameters, it acts similar to a function in that it can return a return-value (accessible via the wait).

The rawthread construct allows for greater flexibility over the standard library's thread in defining what behavior can be concurrent, at the cost of introducing the complexity of truly asynchronous behavior. This should only be used when you really know what you're doing.

threads are primarily used to indicate outputs that can be reordered. Normally, variable writes (including outputs) are treated as order-matters by default, meaning that no reordering of variable writes can be done to optimize the program unless some of those writes are in a different thread. If order doesn't matter, threads should be used to indicate that.

Most of the time, threads should be very short, and executes some code that outputs something.

logger.i['hi'@] ; always written first var data = getData[] thread: logger.i[data] ; might be written second or third logger.i['hello'@] ; also might be written second or third

creates a new scope.

A thread returns a future object (with properties wait and val) that is resolved when the thread completes. The object has the additional members:

weak

Can be set to true to make the thread weak, meaning it isn't counted in the number of running threads. Setting it to false makes the thread non-weak (adding it back to the thread count if it isn't counted already).

cancel[timeout callback] Triggers a cancellation exception to be thrown from the current execution point in the thread's current continuation. The cancellation exception has the time at which the timeout was started as its timeout property, which can be used to judge what work to prioritize if it has recovery work it wants to attempt. The timeout is an optional unixtime value representing, the amount of time to wait before calling the callback function. If a timeout is passed but no callback function is passed, then the thread will be killed when the timeout is reached. If neither timeout nor callback are passed, there will be no timeout and the thread will only die if it completes on its own (after dealing with the cancellation exception).

The returned future also has a destructor that, when run, instructs any exceptions thrown from the thread to be passed to the uncaughtExceptionHandler only if val or wait have not been called. This means that the uncaughtExceptionHandler will only be called for exceptions that have no chance of being thrown elsewhere.

thread.count The number of non-weak threads that are currently running.

[ ; object-inheritance statement :[ ; member statement memberName : memberAlias member statements ] ; object-inheritance statement object2 :[! memberName : memberAlias memberNames ] object-inheritance statements ] : : memberName : memberAlias member statements object2 :! memberName : memberAlias memberNames object-inheritance statements

Makes the privileged members of a previously defined object (or ) available for use in the object (or ) being defined, somewhat similar to extends in java. It has mixin-like semantics: the object gets a copy of the private and public privileged functions and variables of each object passed in the parameters. An object with multiple inheritance involving name collisions must explicitly declare which object it will get its member from.

  • should be a lima expression that returns an object to inherit from.
  • Everything inside the normal colon section ( :[ ] or just v:), including the colon-bracket, is optional. Indicates that only the listed members will be inherited from the .
  • Everything inside the not-colon section ( :[! ] or just v:!), including the not-colon-bracket, is optional. Indicates that all members of the will be inherited, except for the ones listed without a memberAlias. Any member *with* a memberAlias will be included as that alias.
  • For a given object-inheritance statement, there may be up to one colon-bracket operator (either the normal colon-bracket, the non-colon-bracket operator, or neither).
  • memberName is the name of the member to inherit (or not inherit in the case of the not-colon-bracket operator). This can use the special operator construct (operator[op]) to indicate the symbols for an operator (whether referring to the original symbol, or aliasing an operator with a different symbol). Similarly, you can use the special numberPostfix construct (numberPostfix[postfixCharacter]) to selectively mix in number postfixes.
  • memberAlias is a name in which the calling object will inherit the member given by memberName. These are optional, defaulting to calling the member by its original name. With the colon-not operator, any members that are aliased are *included* with that alias (not excluded).
  • memberName and memberAlias can use dot syntax to describe how sub-members should be inherited.
    mix: someObject: a.x ; inherits someObject.a.x (but not any of a's other members) mix: someObject:! b.x ; inherits all of someObject, including b, except b.x mix: someObject:! c.x : c.theXX ; inherits all of someObject except c.x, including c, ; and then aliases c.x as c.theXX
Note that colon and colon-bracket syntax can be mixed and matched.

You can use the result of an expression for memberAlias in this macro by prefixing the expression with the @ sign.

mix[ redrobin ; all members of redrobin are inherited someObj2:[! drogon puff.p ] ; all members are inherited except drogon and puff.p someObj3:[! dang yucky ] ; all members are inherited except dang and yucky someObj4: ; members inherited under aliases a : memberA b : @"memberB" ; using the result of a string as the alias name d.x : f.z ; aliasing a submember onto another member entirely (f) e.x : @"e.xx" ; string member names for submembers use dot syntax someObj5:! operator[<] ; inherits all members except the < operator garth : garthAgain ; and someObj5.garth is inherited under the alias garthAgain someObj5: operator[<] : operator[<<<] ; inherits an operator as a different operator symbol ; inherit members of other modules load["/C/src/lib/aFile2.lima"] ; absolute path load["../lib/aFile.o"] ; path relative to the current file load["../lib/aFile3.lima"]:[specialMethod fancyMethod] ]
Aliasing and selective inheritance is used to make inheritance and module inclusions completely unambiguous.

Since modules are themselves objects, (in conjunction with load) can be used like #include/import in C/Java respectively.

nests. This means that if you objectA into objectB, and objectB into objectC, members from objectA will be available in objectC. When you want to allow objectB to use objectA's members without those members also ending up in objectC (for example when mixing a module as a dependency), simply use private:

private mix: load["dependency1.lima"]

atomic atomicHandle atomic atomicHandle[ ] atomic atomicHandle name: blocks atomic atomicHandle[ name: blocks ]

Calls each block in an atomic way - all activity that happens within each block will not be visible to other code until that function is complete. The blocks are also mutually-atomic - each block will be run on the program state as it was before the atomic block. Any thread created inside the atomic block will begin once the atomic block ends unless that thread is waited on in the atomic block. If there is a conflicting change, as soon as the conflict is detected, the block that caused the conflict throws an exception from that point.

If an exception happens in one of the blocks and is not handled within the atomic block, an exception will be thrown out of the atomic block that has an errors property, containing the list of exceptions that happened in that atomic block. Any state changed within it will be kept (ie not rolled back).

The atomicHandle is a handle-object that's accessible within each of its named blocks. The returned object has the following methods:

atomicHandle.rollback[name] Rolls back any changes made in the block with the passed name.
atomicHandle.rollbackAll[] Rolls back any changes made in all blocks in the atomic block.

Note that in lima, happens-before relationships are guaranteed for all variables (like volatile variables in Java [but not C]), tho that volatility may be optimized away by the compiler if possible.

mut x = 50 rawthread: var y = x logger.i['y: 'y] atomic printX: x -= 1 logger.i['x-1: 'x] x += 1

In the above code, the atomic block prevents y from ever getting the value 49. Of course, this is a contrived example since you could easily just print out x-1 instead of decrementing x. There can be more complicated cases where you need a function that mutates shared state, but you don't actually want that state to be visible until you fix it somehow. You should always avoid mutating shared state into an inconsistent state, and atomic blocks are one method of doing that.

var x=7 atomic plusX plus1: x += 1 plus2: x += 2

The above code throws an exception.

var x = 10 var y = 20 atomic xy x: x = 15 y: y = 25 xy.rollback['y']

In the above example, the value of x is changed to 15, but the value of y remains 20.

override assignmentStatement overide assignmentStatement

Allows you to override an existing member or operator of an object inside an object literal. Since usually redefining an exiting object member or operator inside an object literal will throw an exception, this allows you to override a member or operator without getting an exception.

var parent = {a=5} var child = { mix[parent] override a=100 override operator[-] = fn this other: ret other-this other this: ret this.x-other.x }

This uses the allowMemberOverride attribute.

future When run on a statement (or set of statements), returns a future object. Exceptions thrown from the statement will be thrown from accesses to the future's properties rather than from the statement in-line.

The returned future object has the properties:

val The value returned by the statement. Throws an exception if the statement throws one. Note that this doesn't (by itself) ensure that the statement has completed after that line. Like most lima code, it really returns a lazily calculated value - meaning that code will only block when it needs a value inside val to continue. This property can only be set once, and throws an exception if set again.
wait: statements A macro that runs the statements only after all inputs and outputs inside the original statement are complete. Note that the statements inside the wait are *not* asynchronous, statements after the wait that rely on values determined inside the wait still implicitly wait on those values (just like normal lima code). Throws an exception if the statement throws one.
done A pseudo-member that resolves the future (indicate that wait statements can now be run). This can only be run for futures created via the copy operator of future. Can only be run once.
ret[value] A convenience method that both sets the val property and calls done. Returns the future itself (also for convenience in creating immediately resolved futures).
The returned future object has the operators:
  • ! - The interface operator returns a future type (the type returned by future's copy operator.
  • [type] - The bracket operator returns a specialized future type that resolves to the passed type.
; pretend this is creating a database record and returning its new id ; and also pretend the id is created on the client (like it usually is with mongo db), ; so the id is ready before the document is created var x = future: createNewDocument[] ; store the id somewhere ; lima is not obligated to wait until the whole file has loaded ; so this line can run before the document is finished being created outputId[x.val] ; this might not find the document, since lima isn't waiting for the creation of the document to be complete ; var document = loadDocument[x.val] ; here, we wait until the document is done being created before trying to load it x.wait: var document = loadDocument[x.val]
This is used to precisely control the order statements are executed in since, in Lima, out-of-order is the norm.

A future can also be explicitly created and resolved. The copy operator of future returns a new unresolved future.

var f = future thread: var x = 0 while x<100: x+=1 if x == 50: f.val = x break f.done ; resolves f (indicating that things waiting on it can now run) f.wait: logger.i["its finally done! f is: "+f.val]
If the future variable goes out of scope while containing an exception that hasn't been thrown, it will throw the exception (so that the exception isn't lost).

jump is like goto in C. It isn't meant to be practically used in everyday code, but exists for the flexibility to write assembly-like code. Jump really does a computed jump (ie a "computed goto"). Jump can't be used to jump to a continuation in a different thread.

; the following loops 5 times using jump mut int x = 5 var a = contin x -= 1 if x == 0: jump b else: jump a hoist b = contin
Note that b can be used (ie jumped to) before that line is hit, because the variable is hoisted.

can also be used like longjmp in C's setjmp.h. If a is set and then used in a directly or indirectly calling function, it will restore context as well as jumping.

func = fn [contin back: jump back ; will jump to the command "logger.i['finally back']" logger.i['this never prints'] ] main = fn [ func[here] hoist here = contin logger.i['finally back'] ]

Note that once the continuation goes out of scope (when the function its in returns), it is effectively deleted and attempts to jump to it will cause nil pointer exceptions.

assert[ statement probability=1 ]

Asserts that the statement is true at a given point in the code with a probability of probability*100 percent (probability must be a number in 0 through 1). It is used to give the compiler the information neccessary for certiain types of optimization. asert is an alias for assert.

Note that assert does not exit the program (like it does in C) or throw an exception. Again, assertions are only used to give optimization information to the compiler. If the assertion fails at run-time, the code continues as normal. This may cause undefined behavior, and so if you want to take some action on incorrect input, also check it in normal code and throw an exception (or take some other action) in that case.

; you might want to assert that the input to your program will be a list of integers from 1 through 10 main = fn: assert[con.in.split[''@].join[1<=v<=10]] ; or you might want to assert something about a function's arguments ; this kind of thing will be used by the compiler to deduce contraints on the external input to your program somefn = fn x y: assert[x<y]
optimize[ metricStatement ]: statements optimize[cputime memory power: metricStatement ]: statements Creates an optimizer macro, which will optimize the code its run on such that the value of the passed metricStatement is minimized. The variables cputime, memory, and power are implicitly defined, but may be renamed by passing names in before a colon (eg optimize[c m: c+4*m+10*power]). If other variables are used in the optimizer, the value they are taken as is the value they will hold at the *end* of the statements the optimizer is run on. Any optimizer found inside the statements is ignored (so only the top-most optimizer is used).
var timeWeight = fn msElapsed: ; function for determining the weight of running time if time < 300: 2*msElapsed ; short amount of time is ok else: 600+(msElapsed-600)^1.2 ; long amount of time isn't - exponentially bad ; cputime is 10 times as important to optimize as memory ; and optimize the time it takes to complete it optimize[10*cputime + memory + timeWeight[msElapsed]]: var start = time.before: var end = time.after: doStuff[] var msElapsed = start - end
match rawInput startColumn: run arg: [ ]

A definition. rawInput is the string of all the characters that come after it as a parameter, up until one of the characters isn't indented at least one space from expression the macro is called in and that line doesn't start with an end-bracket, end-curly-brace, or end-paren. startColumn is the zero indexed column number that the first character of the input starts at (relative to the expression's indentation level). For example, if startColumn is 5, then input.split[@''][1][5] is vertically aligned with input[0]

Inside the `match` statements, the programmer defines how much of the rawInput to consume - eg by using Lima's function. It must then return an object with the properties:

  • consume - The number of characters the macro consumed.
  • arg - A value to be passed to the `run` statements.
Execution is done separately from determining the number of consumed characters (in the `run` statements) because execution doesn't necessarily happen in some cases, for example when the macro is used as a default parameter value for a parameter set that doesn't match the arguments. Macros should not mutate external state in the match statements unless that is taken into consideration.

The `run` block gets the consumed source string as its input, and the statements run in that block are statements to execute when the macro is to be executed.

The startColumn can be used to allow defining behavior based on vertical alignment between the first line and subsequent lines. For example:

myMacro a: ret a a b: ret b

A macro value must be followed by the ~ operator to be accessed without its macro being evaluated.

macroName... A macro call.
numberPostfix[postfixCharacter] number postfix: ; statements

Defines a new number postfix where postfixCharacter must be a string containing a single character - the character that the postfix starts with.

Number postfixes macros are macros that act on a string written on the end of numeric literals. The first character of the postfix must start with a-z, A-Z, or _ (underscore) and the rest can contain those characters and additionally 0-9 and . (dot).

Example:
numberPostfix['t'] number postfix: return number * parse.number[postfix] 5t3 ; Returns 15.

The number parameter will hold the number before the postfix, and the postfix parameter will hold the postfix as a string. Whatever the statements return (using ret just like in a function) will be returned from the number plus postfix expression. Even tho number postfixes can be defined based only a single character, multi-character number postfixes can be emulated by requiring specific characters as input and/or switching behavior based on those extra characters. Overriding them can be done using the override macro.

Note that doing this modifies the internal number object for the current module only. If you want to pull in number postfixes defined in a different module, you can use mix to do that similar to how you can inherit operators into an object.

The function creation construct. It can create functions/methods and define function-types. creates a new scope. This means variables declared inside it do not persist outside it.
Operators:
:   [ ] fn is a macro that returns a function immediate (aka function object / nameless function / closure / lambda). This is like nameless functions in javascript and is pretty similar to functions in C, java, or php. This uses Conventions A, C, and D.

The function's are defined as a - an expression that either ends with a colon. For a function, the expression inside the is very similar to an object's .

var w = fn[ a b c: ; a b and c are taken as params doSomethingWith[a b c] ] var x = fn [ a b c: ; a b and c are taken as params again doSomethingWith[a b c] ]
If variables in are initialized, it means that it has a default value that can be overridden if an argument is passed in for that parameter. Default parameters can appear anywhere in the parameter list, not just at the end of the parameter list. The only constraint is that all parameters with defaults be consecutive (all one after another), and if a splat is used, it comes after all the default parameters. Note that parameter types and default values are evaluated on every function execution, and so the object itself can be used in method parameters (eg as a type by using the interface operator).
fn! x = fn a='myDefault' b: logger.i[a ': ' b] x['moo'] ; prints "myDefault: moo" ; variable defaults var z = 'default1' fn! x = fn a=z: logger.i[a] x[] ; prints 'default1' z = 'toooo' x[] ; prints 'toooo' var w = fn a=b b: ret a*10+b w[1] ; returns 11 w[2 3] ; returns 23 ; how parameters are filled with arguments fn! y = fn a b=11 c=12 d=13 e: ret a+b+c+d+e logger.i[y[1 2 3]] ; returns 31 (1+2+12+13+3)

In addition to what you can do in an object's , you can also write an object's members as parameters using , which denotes that the argument passed in is directly assigned to the member in the parameter space. This is a special case where private members can be accessed via this.

x = { a b func = fn this.a this.b: } x.func[2 3] logger.i[x.a x.b] ; outputs 23
Destructuring assignment can also be done in a function:
var f = fn {a b} c: return a+b-c var x = {10 20} f[x 3] ; returns 27

Variables defined in a function may not conflict with any variable already in scope - i.e. you can't shadow variables in lima functions.

Functions use multiple dispatch. The list of parameter formats is checked in-order, and the first format matching the input parameters will be run.

fn! A = fn int[x] int[y]: ret x+y int[x] string[y]: ret x.str.cat[y] x[3 5] ; returns 8 x[3 'five'] ; returns "3five" ; this definition throws an exception, because the signatures conflict ; for example in the case B[3 0] was called fn! B = fn int[x] int[y]: ret x+y real[x] bool[y]: ret x.str.cat[y]

All functions are members of some object (even top-level functions are module methods). Therefore, can be used inside functions. However, is an upvalue (a variable closed over by a closure), and so remain pointing to the original object they were defined in when that function is copied elsewhere. This means that you can copy an object's method to somewhere else, and when you run the function, it may mutate the object you copied it from. To transfer the object referenced by , an object must inherit the function using .

Function values have the following operators defined for them:

[arguments]

A function call. arguments are either a simple ordered list of values, a named list of values, or combination of both.

If arguments is a simple list (ordered parameters), then each of the function gets a reference to each corresponding in order. If has parameters that are assigned values (named parameters), then the parameter corresponding to the being assigned is set. You can use both ordered and named parameters as long as all the ordered parameters come first.

function[1 2 3] ; passing 3 arguments in a simple list function[paramX=2 paramY=5] ; passing 2 arguments with named parameters function[1 2 parameterB=3 parameterC=9] ; using ordered and named parameters function[1 2 5:3 6:9] ; using ordered and explicitly-ordered parameters (parameters 2 3 and 4 get nil)

Note: arguments are passed by reference. If a parameter is modified in a function, it will be changed outside the function as well. Note that because Lima is value-based, the parameter can just as easily be a literal or statement as it can be a variable, but if it is a direct literal or statement passed into the function, nothing in the scope of the function call would be able to see any changes to that value, since there would be no reference to it.

The IDE should give the programmer insight into if a function mutates the state of passed in variables (as well as insight into how it can affect the module- or global- state). Of course after optimizing, the program might end up passing by value under the hood if that is more efficient.

Applicative order for function parameters are left to right - in the order they appear. So func[A[] B[]] will execute A first, then B.

Returns a type that matches a function that can take all the combinations of parameters (type, name, and order) that the operand takes.
fn[x:]! f1 ; fn that takes one parameter named x fn[x: x y:]! f2 ; fn that takes one or two parameters fn[int x:]! f3 ; fn that takes a single integer parameter fn[a:]! f4 = fn a: return a fn[int[a b]:]! f5 = fn int[a] int[b]: return a+b fn[int a: string a:]! f6 = fn int[a] int[a]: return a+1 string[a]: return a.len var add2 = fn int x: ret x+2 var appendOrAdd2 = fn string x: ret x.cat['2'] int x: ret x+2 appendOrAdd2! newAppendOrAdd2 = fn ; matches the interface of appendOrAdd2 string x: ret 'hi' int x: ret 99 add2! newAdd2 = fn ; matches the interface of add2, since it can handle an int parameter x string x: ret 'hi' int x: ret 99 add2! funcA = fn x: ret x-500 ; matches the interface of add2 appendOrAdd2! funcB = fn x: ret x-500 ; throws an error, since the it can't handle a string add2! funcC = fn h: ret h-500 ; errors because the parameter name doesn't match

Function values have the following methods defined for them:

fn.raw match arguments: statements... ret transformedArguments run transformedArguments: statements... fn.raw[ ... ]

Returns a function that matches and transforms some input arguments and then runs some statements using the transformed arguments. The match argument should be a function that takes in a list of function parameters and returns either nil if the arguments don't match, or an object with the following parameters if the arguments do match:

  • arg - A value to pass into run
  • weak - If true, means the arguments are considered "weak" for the purposes of operator dispatch.
Note that the parameters arguments and transformedArguments are simple names and cannot be typed or defaulted. Also, `match` must come first, and `run` second.
var add1 = fn.raw match arguments: if arguments.len == 1: ret {arg: arguments[0] weak:false} run arg: ret arg+1 logger.i[add1[2]] ; prints 3 add1[2 3] ; throws an exception because the arguments don't match

sideEffectLess

Returns a function that produces no side effects. The output behavior (return value) is preserved. Values pointed to by input parameters (including the calling object) may still be changed.

functional

Returns a function that produces no side effects and does not mutate input variables nor the calling object. Does not change output behavior (return value).

isolate

Isolates the function from modifying any external state (produces no side effects and does not mutate input variables) and returns an object representing all the state it would change if it weren't isolated. The object returns contains the following members:

ret The return value of the function.
effects An object where each member describes a variable the function would have modified if it weren't isolated and what its new value would have been. The structure of the object is the same as the output of dif.

inverse

Returns the inverse of the function - a function that will return the inputs of the original function given an output of the original function, if possible. Takes one parameter - the output of the original function.

Running the inverse function will return a the full inverse of the function if possible (ie. when there is a one-to-one mapping from outputs to inputs of the original function for a given input to the inverse-function). If the function cannot be inversed for a given input, an exception will be thrown.

withCallingScope[object]

Returns a function that will have the passed object as the callingScope rather than the actual calling scope.

Special members only available inside functions:
ret

is a macro that returns a value from a function. To return nothing, call with nil.

var cow = fn: ret 'cow' logger.i[cow[]] ; outputs 'cow'

Functions return by value. If a function wants to allow a returned variable to be used and modified, either a reference variable pointing to it must be returned (using the ~ postfix operator), or you can use ref's bracket operator to return a variable by reference.

fn!x = fn a: a+=1 ref! r ~> a ret r~ fn!x = fn mut a: a+=1 ret ref[a]

Because is a macro, it can be passed to other functions so a function is able to return from a different function higher on the stack.

var f = fn returnFunction: returnFunction 5 var f2 = fn: f[ret~] ; causes f2 to return 5

has the following members:

contin The continuation to which the function will return to when it returns. This may be in the middle of an expression.
result Holds the result that the function will return to the calling context. This allows the programmer to separate the act of deciding the result of the function from when it returns to the calling context. Conceptually, ret used normally first sets ret.result then jumps to ret.contin.

thisfn References the current function itself. Calling this will act just like using the function outside itself.
callingScope

Returns an object representing the calling scope. This allows you to manipulate values in the calling scope and create new variables in that scope. Note that callingScope.callingScope is explicitly forbidden so modules don't have unlimited access to the call stack. You can however pass callingScope into a function to give it access to its granparent scope, and if you set a variable to the callingScope, a called function can access that variable to inspect the granparent scope.

's postfix operator returns a type that only matches functions (not specifying parameters or return values).
[[returnType]] [[:ParamType1A 2A EtcA, T1B T2B, EtcB]] [[returnType: ParamType1A ...]] Returns a type that matches a function whose arguments-list (and optionally, return value) is constrained to the types passed in. Can specify a return type, a set of parameter-type dispatch lists without specifying parameter names, or both.
fn[[]] ; same as fn! fn[[nil!]] f ; a function that returns nil and takes any number of arguments fn[[int]] ; a function that returns an int and takes any number of arguments fn[[:int]] ; a function that takes an int and can return anything fn[[string:int]] ; takes an int and returns a string fn[[: int, int int, int string]] ; takes either an int, two ints, or an int and a string fn[[int: int, string]] ; takes either an int or a string and returns an int ; this can be combined with a function value's ! operator to specify return types and parameter names fn[[int]]&fn[int a:]! f ; takes an int named `a` and returns and int
Methods:
paramType type1 type2 ... Returns a function type that requires the parameters to have certain types and names. Each argument can either be a type value or an object where the first element is a type value for that parameter position and the second element the name of that parameter.
fn.paramType[string~] f ; a function that takes a string fn.paramType[int~ int~] f ; a function that takes two ints fn.paramType[{int~ 'a'}] f ; a function that has a single int parameter named a fn.paramType[{string~ 's'} {int! 'i'}] f ; etc ; combine these to describe a function with multiple dispatch fn.paramType[int~]$fn.paramType[string~] f ; A function that takes an int or a string
condType testFn[typeDispatchLists] Returns a function type that matches function values whose parameters cause the testFn to return true. The typeDispatchLists is a list where each element is an object representing a parameter, and each parameter object is a list where the first element is a type and the second element is the parameter name.
; this represents a function where the first two parameter types must be the same fn.condType[fn dispatch: ret dispatch[0][0]~ == dispatch[1][0]~ ] f

Core Stack Attributes

If set to true, denotes that external inputs to the program within a statement are ready immediately (ie at compile time). Is used by the ready macro.
outReady
If set to true, denotes that external outputs from the program within a statement are ready immediately (ie at compile time). Is used by the outReady macro.
uncaughtExceptionHandler Holds function that is run in cases of uncaught exceptions. It takes a single argument that should be an exception object. This attribute is used by asyncTry and is the function called when an exception reaches the top of a thread's stack without being caught.
collate An attribute that holds the collation (the order of characters).
atr[collate={'a' 'b' 'c' 'd'}]: var w = 'a'..'c' ; w gets {'a' 'b' 'c'} atr[collate={'t' 'r' 'd' 'x'}][ var x = 'r'..'x' ; y gets {'r' 'd' 'x'} var y = "tea" < "dog" ; z is set to true (since in this collation, 't' comes before 'd') var z = "trd" < "tea" ; an exception is thrown since the collation doesn't define a relationship between 'r' and 'e' ]
Note that there is no default collation. If a comparison or range depends on a character that isn't included in the active collation, or there is no active collation, an exception will be thrown.
casemap An attribute that holds the upper-case/lower-case mapping for characters. The keys represent the lower case letter, and the values represent the upper case letter. Any letter that is neither upper case nor lower case should be considered both (and thus be its own key and value).
atr[casemap={'a':'A' 'b':'B'}]: var x = 'a'.upper ; x gets 'A'
Note that there is no default casemap. If the upper or lower string methods are used for characters that aren't defined in the active casemap, or if there is no active casemap, an exception will be thrown.
encodingFail An attribute that contains a function that is called when a character encoding can't encode a character in a lima-string.
atr.encodingFail encoding character encodingFail takes the encoding object and the character that couldn't be encoded as parameters, and the character it returns should be encoded in its place. The default of this attribute is a function that throws an encoding exception. Changing the default of this attribute is used to do things like insert default characters into an encoding, as a graceful failure mode - for example, displaying a default character (like '?' or '◻' in place of unencodable characters.
allowMemberOverride When set to false, attempts to over write an existing object property inside an object literal will throw an exception. When set to true, no exception will be thrown for that reason. The override macro uses this.
atr.strongError(errorObject) This is a function that returns a strongError that the compiler will fail on if errorChecking is set to "strong" in the module's package.lima. Types throw a strongError if a type doesn't match them. Resetting this function can allow you to selectively disable a strong error being thrown by a module your program depends on.

Core Module Attributes

exitFn This attribute contains the function to be run when exit is called, and can be used to override it. It takes the parameter "code" - which should be a numeric exit code.
fileObject This attribute contains the object accessed via file, and can be used to override it.
dirObject This attribute contains the object accessed via dir, and can be used to override it.
systemObject This attribute contains the object accessed via system, and can be used to override it.
dnsObject This attribute contains the object accessed via dns, and can be used to override it.
socketObject This attribute contains the object accessed via socket, and can be used to override it.
ipv4Object This attribute contains the object accessed via ipv4, and can be used to override it.
ipv6Object This attribute contains the object accessed via ipv6, and can be used to override it.
udpObject This attribute contains the object accessed via udp, and can be used to override it.
tcpObject This attribute contains the object accessed via tcp, and can be used to override it.
httpObject This attribute contains the object accessed via http, and can be used to override it.
httpsObject This attribute contains the object accessed via https, and can be used to override it.
inputsObject This attribute contains the object accessed via inputs, and can be used to override it.
bits An "encoding" that represents raw bits - ie no encoding.
limaEnc8

limaEnc8 is a physical (bits) representation of what a string is in Lima. LimaEncoding can represent any string from any encoding, encapsulate the encoding within it if necessary.

LimaEncoding is based on Unicode. It represents all Unicode characters, with the exception of all the control characters other than the newline - the excluded codepoints are U+0000 - U+0009, U+000B - U+001F, and U+007F - U+009F. LimaEncoding always uses big-endian byte ordering, and thus does not have a byte-order mark. The Unicode characters LimaEncoding supports without using the alternate character markers (described below) are called LimaEncodings core characters. limaEnc8 stores unicode characters in UTF-8 format.

It also adds the following codepoints:

Codepoint Name Description
U+000B Header Marker

Marks the beginning and ending of the LimaEncoding header, which contains the list of alternate encodings contained in the string. If this marker isn't the first codepoint of the string, the string has no alternate characters. The header is represented as:

{U+000B, <encodings>, U+000B}
where
  • <encodings> is an ordered list of encodings where each encoding is represnted in the following way:
    {<length>, <encodingName>}
    where
    • <length> is a 16-bit length of the number of bytes in <encoding> minus 1.
    • <encodingName> is the string representation (ie the standard name) of the encoding represented only by core-characters of limaEnc8 (ie. the subset of UTF-8 without control characters).

U+000C Space-extension codepoint

Not a character by itself, this codepoint is paired with a second byte indicating the width of the space minus 1. For example, a 4-space width space character would be written as {U+000C, 3}.

This is intended to replace the tab character. With extended spaces, you can specify how much space is being created (like a space) while also identifying as a single character (like a tab) so that programs can handle selections and backspaces like tabs.

The extended space character has one additional member:

width The width of the extended space. E.g. ""%4.width is 4 spaces long.
name name holds "extendedSpace".

U+000D Alternate character marker 1

Also not a character by itself, this codepoint indicates the beginning of a character that doesn't have a unicode representation (or is one of the excluded unicode control characters). LimaEncoding stores the name of the encoding that character is from with an index in a header section of the string format.

A non-unicode character would be written as:

{U+000D, <encodingByte>, <characterLength>, <characterBytes>}
where
  • <encodingByte> is an 8-bit index of the encoding (in the encoding-list in the header)
  • <characterLength> is an 8-bit length of the number of bytes in <characterBytes> minus 1 (so a binary value of 3 indicates that <characterBytes> is 4 bytes long).
  • <characterBytes> is the bytes of the canonical representation of the character in the encoding its from.

This allows any character from any encoding to be stored in a string encoded into LimaEncoding. Note that when the excluded Unicode control characters are transcoded to LimaEncoding, they are translated using the alternate character marker in the same way as characters from other encodings.

U+000E Alternate character marker 2

The alternate character marker allows for characters with representations longer than 256 bytes long. This is the same as U+000D except the character is represented as:

{U+000E, <encodingByte>, <lengthLength>, <characterLength>, <characterBytes>}
where
  • <lengthLength> is an 8-bit length of the number of bytes in <characterLength> minus 1.

Since most characters have a unicode representation, special characters will be very infrequently used. So strings encoded in limaEnc8 will usually be identical to its UTF-8 counterpart.

limaEnc16 Exactly like limaEnc8 but the representation of the core-characters is UTF-16.
utf8
ASCII

Parameterized Encodings

utf32 tabWidth endianness

UTF-32 with the given endianness. endianness can either be 'big' or 'little', defaulting to 'big' if not passed. If tabWidth is passed, tabs will be converted to extended-spaces with the width tabWidth. If tabWidth is not passed, tabs will not be decodable.

A note about unicode: Lima leaves any definitions for word/line/sentence separators or mid-word hyphening to outside libraries.

utf16 spacesInATab endianness UTF-16. The parameters mean the same thing as for UTF-32.
dosText encodingName This encoding handles windows-specific file encodings. When encoding, this first encodes with the encoding named encodingName, then converts regular newlines to dos newlines and adds the end-of-file characters (ascii value 26) at the end of the file. When decoding, this first converts dos newlines to regular newlines and ends the file at the first end-of-file character, then decodes using the encoding named encodingName. This replaces opening files in "text mode" in a language like C.
url encodingName When encoding, this encoding first encodes with the encoding named encodingName, then url-encodes the result. When decoding, this first url-decodes the string, then decodes using the encoding named encodingName.
Infer An inferred type. This is a special type that takes on a type based on the first value the variable is given. If the variable is initialized with an expression that has a type (eg a variable, or a function call), the variable will get that type. If the variable is initialized with an expression that doesn't have a type but is a string or a number, the variable will get that the type string or number (respectively). Otherwise, the variable will get an interface-type based on the value. For example:
Infer a = 5 ; a gets the type 'number' Infer b = "hi" ; b gets the type 'string' Infer c = {x:5} ; c gets the type '{x:5}!' Infer d = b ; d get the type 'string'
Boolean values (0 and 1).
bits A type conforming to the structure of the number.bits object, which is an array of boolean values, that also has special methods and operators for bit operations. The type requires the same num, ones, twos, none, dec, chunk, and dechunk members as the output of something like 5.bits.
Signed integers. Represents all integers greater than - and less than . Has the following methods:
parse string s Attempts to parse the string as an integer and return the value. Throws an error if the value isn't an integer.
roman string s Attempts to parse the string as a roman numeral and return the value. Throws an error if the value isn't a roman numeral.
english string s Attempts to parse the string as plain english and return the value. Recognizes numbers ignoring spaces and dashes between word-parts. Throws an error if the value isn't an english number.
engOrdinal string s Attempts to parse the string as an english ordinal and return the value. Throws an error if the value isn't an english ordinal.
To define an unsigned integer: type.set[0..00] unsignedIntVariable
Signed real number. Represents all rational numbers. Has the following methods:
parse string s Attempts to parse the string as a real number in decimal format (eg "1.334") or fraction format (eg "3 3/4) and return the value. Throws an error if the value isn't a real number in decimal format.
sci string s Attempts to parse the string as scientific notation or engineering notation and return the value. Throws an error if the value can't be parsed.
An alias for 0, meant to represent boolean values. The only difference between and 0 is that 's str method returns 'false' instead of '0'. 0 and compare as equal with ==.
An alias for 1, meant to represent boolean values. The only difference between and 1 is that 's str method returns 'true' instead of '1'. 1 and compare as equal with ==.
con con represents the console (stdin, stdout, stderr). Returns a chan object where in represents stdin and out represents stdout. Does not inherit the close method. The conObject attribute can be used to override console functionality.
Members of con:
err Operates just like the out member except for stderr instead of stdout.
enc Returns a string containing the standard name of the encoding used by the console.
Methods of con:
cr A "real carriage return" (returns to the beginning of the line).
bs Backspace. Note that the affect of this function depends on the state of the console. For example, you can't usually backspace a newline when writing to the console.
logger An object that has methods for logging.
Methods:
[] level message data Logs a string message at a particular string level, including some optional data. This just passes this info to atr.logger, which logs to the console by default.
log['debug' 'We got one!!!' {entity:"Full-roaming vapor"}]
Its strongly recommended that the level used is one of the ones listed in Convention H.
d message data Short for log['debug' message data].
i message data Short for log['info' message data].
w message data Short for log['warn' message data].
e message data Short for log['error' message data].
severe message data Short for log['severe' message data].
shape An object that has methods for representing and operating on shapes of any kind including volumes, areas, lines, and points.
Methods:
Returns a type that enforces variables to hold shape objects.
shape! a = shape[3 4]
coordinate1 coordinate2 coordinate3 coordinates Creates a shape object representing a point in n-dimensional space. Any number of coordinates can be used; coordinates default to 0 when unspecified.
var a = shape[1 4 0 6]
coordinateList1 coordinateList2 coordinateLists Creates an shape object representing a line-segment, surface, or other n-dimensional shape. Every point on the surface of any line connecting two sets of coordinates is included in the shape, as is any point contained within the closed object created that way.
var a = shape[{1 4 0} {1 4 0}] ; a line-segment var b = shape[{1 4 0} {1 4 0} {0 4 3}] ; a area/surface made from 3 points var c = shape[{1 4 0} {1 4 0} {0 4 3} {1 4 3}] ; a volume made from 4 points var d = shape[{1 4 0 9} {1 4 0 3}] ; 4-dimensional line-segment
relation Creates an shape object represented by the equation.
var a = shape[relation[x y: x^2=5*y^2]]
shape1 shape2 Creates an shape object created from two regions where all the space between the regions is filled in, just like in the bracket-operator with coordinate-lists.
var a = shape[ shape[{1 4 0 6}] shape[{1 4 0 9}] ] ; a line-segment var b = shape[ shape[{0 0} {1 2}] shape[{3 0} {2 2}] ] ; a trapezoid
shape objects have the following binary operators:
a & b Set intersection (conjunction). Returns a shape that contains the regions that exist in both a and b.
a $ b Set union (disjunction). Returns a shape that contains the regions that exist in either a and b or both.
a !$ b Set exclusive disjunction. Returns a shape that contains regions that exist in only a and regions that exist only in b but not regions that exist in both.
a == b Equality. Returns true if all the regions contained in a are also contained in b.
a <> b Same as ==
a !<> b Same thing as !(a <> b)
a < b Proper subset of shapes (returns true if a is a proper subset of b).
a > b Proper superset of shapes (returns true if a is a proper superset of b).
a <= b Improper subset of shapes (returns true if a is an improper subset of b).
a >= b Improper superset of shapes (returns true if a is an improper superset of b).
shape objects have the following methods:
dist otherShape Returns a real number distance between the closest points from each shape.
real x = shape[3 1].distance[shape[1 1]] ; returns 2
shape objects have the following members:
center Returns a shape's center point (as a shape).
regions Returns a list of regions where each region is continuous.
surface Returns a shape that describes only the surface of the object (and gets rid of any region within that surface).
zone An object that has methods for representing a region of the world. Coordinates used for this are latitude and longitude (latitude has first ordinality) and are expressed in degrees. The region is not required to be continuous. Inherits from shape.
Additional members of zone
Additional methods of zone
own Returns the default locale set on the computer, or nil if there is none.
time timezoneString timezoneStrings Returns a zone representing the region of the timezone named in the parameters, each of which should be in Olson timezone format.
var z = zone.time["America/Los_Angeles" "America/New_York"] ; returns a zone containing both the PST and EST time zones.
language languageCode languageCodes Returns a zone representing the region where the languages given are official languages. The languages should be in ISO 639-2 format. Throws an exception if a language doesn't exist.
var z = zone.language["eng" "fre"] ; the zones where english and french are official languages
language timeObject languageCode languageCodes Just like the above except it takes into account the time given.
var z = zone.language[time[-500-01-01] "eng"] ; the regions where english was spoken at 500 BCE
country countryCode countryCodes Returns a zone representing the region where the countries given are (at the time the function is called). The countries should be in ISO 3166 format (all representations are accepted and case doesn't matter). Throws an exception if a country doesn't exist.
var z = zone.country["arm" "cn"] ; a zone containing the regions of China and Armenia
country timeObject countryCode countryCodes Just like the above, except it takes into account the time given.
var z = zone.country[time[-500-01-01] "arm" "cn"] ; a zone containing the regions of China and Armenia
Additional members of zone! objects
timezones A list of tz database timezones intersecting the zone (in Olson timezone format).
Additional methods of zone! objects
languages timeObject A list of languages spoken within a zone in order of popularity (how many people speak it) during a given time. If the time is omitted, the current time (when the method is called) is assumed.
countries timeObject A list of countries within a zone during a given time.
collations timeObject A list of collations within a zone during a given time.
An object that has methods for representing and operating on date and time. The internal representation is intended to be continuous and unambiguous (without oddities introduced by time zones, daylight savings time, or leap-seconds). The unambiguous internal representation can, of course, be converted to any time format.
Pseudo-members:
after: statements A macro that returns a time object representing the current time directly after the statements have run.
Time capturing is still allowed on machines that don't have the actual date or time availble to it, as long as the times are only used to create durations.
In Lima, there is no "now". Because statements can be reordered to optimize performance, there would be no way to guarantee that a time is captured both after its preceding statements and before its following statements without destroying lima's ability to reorder statements optimally. So instead, Lima allows you to choose to either bind a time before some given statements or before them, but not both. If you need both, you need to create two separate time objects.
; the following to sets of statements can be run completely in parallel ; which means that ta1 may be the exact same time as tb1 ; and also means that tb2 might even be earlier than ta2 (if 'B.txt' takes less time to download and output) var ta1 = time.before: var ta2 = time.after: var x = downloadFile['A.txt'] outputFile['some/path/A.txt', x] var tb1 = time.before: var tb2 = time.after: var y = downloadFile['B.txt'] outputFile['some/path/b.txt', y] ; the following statement guarantees that the time is as close the console output as possible var t = time.before: logger.i['The time is: 't] ; the following statement throws an exception because t isn't declared until after the logger.i statement var t = time.after: logger.i['The time was: 't]
before: statements A macro that returns a time object representing the current time directly before the statements have run.
resolution A duration object representing the time between "ticks" (between value changes of the clock - ie values of now and ).
accuracy A probability map describing how accurate the clock is likely to be at the moment this member is accessed. The object maps the likely difference, in seconds, to its probability (value from 0 to 1).
; if there's at least a 90% likelyhood the absolute deviation is less than half a second if time.accuracy[[-.5<k.5]] < .9: resyncClock[] ; made up function that might resync the clock from the internet
tai Same thing as time.ts['tai'].
utc Same thing as time.ts['utc'].
unix Same thing as time.ts['unix'].
tdb Same thing as time.ts['tdb'].
Methods:
Returns a type that enforces variables to hold time objects.
time! t = time[2012/04/04 3:00']
ts timeStandardCodename Returns an object with methods for handling dates for a particular time standard. A list of time-standards is below this section. When used with this method, the time standard names are case insensitive.
The same bracket operator time.ts['tai'] has.
p pattern

Macro that creates a time-pattern that can be used to both parse and format time objects. Keywords and strings are concatenated to describe the pattern. The $ operator can be used to indicate that more than one pattern can be used. Parts that are surrounded by parentheses are optional (the whole thing in parens should match, or none of it). Expressions with parens are nestable.

time.p[M ' - ' d] ; parses something like "April - 2" time.p[h ':' min (':' s)] ; can parse both "8:30" and "8:45:20" time.p[h (':') min ((':') s)] ; can also parse "830", "8:4520", "845:30", and "84520" time.p[M '-'$' - ' d] ; can parse both "April-2" and "April - 2" time.p[h$hp ':' min] ; can parse both "8:30" and "08:30"

To use the value of an expression inside time.p, prefix the expression with the @ sign. Time patterns can be used with this method as well as stringable values.

var x = ":" var timeOfDay = time.p[h @x min @cat["|"x"|"] s] time.p[y'.'m'.'d' ' @timeOfDay] ; matches '2000.04.03 3:05|:|00'

Any keyword can also be explitly set with the equals sign. If such an assignment is done, that keyword is ignored when parsing

time['20:00' time.p[M='April' h min] ]

The available keywords and their meanings depend on the particular time-standard being used.

addSimilar originalStandard newStandard convertTo convertFrom Adds a time standard named newStandardbased on originalStandard. The new time-standard and the result of a timeObjects' ts method for the new standard will have all the same members and methods as the originalStandard.
add name staticMembers standardObject Adds a time standard from scratch, named name. staticMembers should be an object containing the members you'll get when you access time.ts[name]. standardObject should be a function that takes a time! object and returns the object you'll get when you access timeObject.ts[name].

Time objects

Time objects represent a non-ambiguous date and time. Time objects do not carry information about any particular location or zone, they represent the absolute time. A time-object can be converted to any representation, even ambiguous representations, for any zone or area.

time objects have the following methods:

format timePattern timeZone translator The same format method timeObject.ts['tai'] has.
ts timeStandardCodename Returns an object with methods for creating modified time objects from the current one related to the given time-standard.
time objects have the following members:
tai Same thing as timeObject.ts['tai'].
utc Same thing as timeObject.ts['utc'].
st Same thing as timeObject.ts['st'].
unix Same thing as timeObject.ts['unix'].
tdb Same thing as timeObject.ts['tdb'].
time objects have the following binary operators:
a - b Difference of times and subtractions of durations.
time[2012/04/05 20:00] - time[2012/04/05 17:00] ; returns an object equal to dur[3:00] time[2012/04/05 20:00] - dur[3:00] ; returns an object equal to time[2012/04/05 17:00]
a < b Time less than.
time[2012/04/05] < time[2012/04/01] ; returns false
a <= b Time less than or equal to.
a > b Time greater than.
time[2012/04/05] > time[2012/04/01] ; returns true
a >= b Time greater than or equal to.

Time standards

Time standards in Lima include both a proper time-standard (a definition of a second under various circumstances) and a calendar standard. A particular time standard will have different but similar methods for parsing dates Standards should usually have a default pattern so the pattern need not be more fully specified, but some standards have the ability to further define a particular date pattern.

tai International Atomic Time (TAI) is a coordinate time standard.

Members available to time.ts['tai']

years A set of dates each representing the beginning of a year in TAI in ascending order.
logger.i[time.tai.years[0]] ; prints "0001-01-01 00:00:00" df time.tai.years[[time[1999] < v < time[2020]]] y: logger.i["It was a dark and storymy night, in the year "y.tai.y"...."@]
months A set of dates each representing the beginning of a month in TAI in ascending order.
weeks A set of dates each representing the beginning of a week in TAI in ascending order. Days of the week start on monday.
logger.i["Lets say all the days of the week. Together now:"@] var someWeek = time.tai.weeks[0] df (1..7).map[someWeek+ v*dur[/1]] v logger.i[v.tai.format[time.p["DW"]]@]
days A set of dates each representing the beginning of a day in TAI in ascending order.
hours A set of dates each representing the beginning of an hour in TAI in ascending order.
minutes A set of dates each representing the beginning of a minute in TAI in ascending order.
seconds A set of dates each representing the beginning of a second in TAI in ascending order.
Note: In all the above members (years, months, weeks, days, hours, minutes, and seconds), key 0 maps to the first of that time-unit starting in year 1 CE, and negative keys map to earlier time-units.

Methods and operators available to time.ts['tai']

dateString timePattern translator

Returns a time object of the dateString as parsed with timePattern. If the string can't be parsed, an exception is thrown. If no pattern is passed in, the default pattern is
time.p[(y('.'m$mp('.'d$dp))) (h$hp':'min(':'sr)) (' 'era)]. translator (optional) is a function that takes in one of the english date-words and returns a word from another language.

time.ts['TAI']["2012/06/04 15:00"] ; June 4th, 2012 at 3:00pm TAI time.ts['TAI']["20120604" time.p[ympdp]] ; June 4th, 2012 (at 0:00, ie 12:00am) TAI var translator = fn x: if x.lower=='april': ret 'shigatsu' else ret x time.ts['TAI']["20120403" time.p[ympdp] translator] ; shigatsu 4th, 2012 (at 0:00, ie 12:00am) TAI
Will throw a parse exception if the string can't be parsed properly.

Will throw a contradictory-date exception if the string is parsed properly, but certain redundant information is inconsistent.

; the following throws an exception, since April 3rd 2012 was a Tuesday time.ts['TAI']["Saturday, April 3, 2012" time.p[DW', 'M' 'd', 'y]]

timePattern should be a value returned from time.p.
dateText Like the bracket operator above, but instead of being passed a string, is instead passed literal text. Always uses the default for timePattern from the bracket operator with two parameters and doesn't use a translator.
time.ts['TAI'][2012.06.04 15:00] ; June 4th, 2012 at 3:00pm TAI

Time pattern keywords available to time.ts['tai']

The following keywords can be used in the pattern. Keywords are either all uppercase or all lowercase. Uppercase keywords always describe a part of time as a word (e.g. 'April' or 'Saturday').

era The era (either 'CE' or 'BCE').
cent The century (eg. time['1994'].cent would return 20)
y The absolute value of the year (for use with era eg. "300 BCE").
yp Shortened year or "year, pivoted" (eg. "96" instead of "1996"). Must be accompanied by the py keyword.
py Pivot year - two digit years of this number or lower are interpreted as in the current century. Two-digit years above this number are interpreted as in the previous century.
; usually, this should be explicitly set rather than matched in a formatted string time['04/03/50' time.p[py=50 mp'/'dp'/'yp]] ; same as time[2050.4.3] time['04/03/51' time.p[py=50 mp'/'dp'/'yp]] ; same as time[1951.4.3]
dy The numeric day of the year (e.g. 51 for time[1986/02/20])

m The numeric month where 1 is January and 12 is December.
mp The numeric month padded with 0 for months less than 10 (eg. 08 instead of 8).
M The month as a word (eg. February).
MS The month as an abbreviation (eg. Feb).
d The numeric day of the month (eg. 20 for time[1993/04/20]).
dp The numeric day of the month padded with 0 for days less than 10 (eg. 08 instead of 8).

dw The numeric day of the week where 1 is Monday and 7 is Sunday
DW The day of the week as a word (eg. 'Tuesday')
DWS The day of the week as a short word (eg. 'Tue')
h Hour in 24-hour time (eg. 15 for 3:00 pm)
hp Hour in 24-hour time padded with 0 for hours less than 10 (eg. 08 instead of 8).
hh Hour in 12-hour time (eg. 3 for 3:00 pm)
hhp Hour in 12-hour time padded with 0 for hours less than 10 (eg. 08 instead of 8).
MER Meridian indicator (eg. "am").
min Minute.
s Integer second.
sr Real number second, includes fractional seconds in the decimal of the number (eg. "8:01:23.54").

TAI members of time-objects: timeObject.ts['tai']

Have the following members:

era The era as a string.
y The number of years.
m The number of months.
d The number of days.
h The number of hours.
min The number of minutes.
s The number of seconds, possibly fractional.
time Returns a time object representing the (now possibly modified) tai time.
The members era, y, m, d, h, min, s can be modified, and modifying them mutates the tai object. Also, for the numeric members (all except era), setting the member to a negative number will roll back that many extra time-units before the next highest time-unit, and setting the member to a number greater than is allowed inside the next highest time-unit adds that many extra time-units.
mut x = time[2008/04/03 2:30:00].tai x.s -= 40 ; x now represents time[2008/04/03 14:29:20] x.min += 100 ; x now represents time[2008/04/03 15:09:20] x.y -=1 ; x now represents time[2007/04/03 15:09:20] x.time ; returns time[2007/04/03 15:09:20] ; note that subtracting a year as a duration is different from subtracting it as a part of a TAI time ; since things like leap years need to be taken into account var y = time[2008/04/03 3:09:20] - dur[1/] ; time[2007/04/04 9:09:20]

Has the following methods:

format timePattern translator Returns a string representation of the time according to the stringFormat. If no pattern is passed in, the default is time.p[y'/'m'/'d' 'h':'min':'s' 'tz]. translator should be able to translate from the english forms of each non-numeric keyword (era M MS DW DWS MER).

st Standard Time. A locality specific time-standard. Has all the members and time-pattern keywords that time.ts['tai'] has, as well as the following additional ones:

Members available to time.ts['st']

jumps A list of time objects each representing the time after a non-linear time jump (usually daylight-savings time jumps). The list is sorted by increasing time.
var now = time.before: var nextTimeJump = time.st.jumps[[v>now]][0] mut timeOneSecondBeforeJump = nextTimeJump timeOneSecondBeforeJump.st.s -= 1

Methods and operators available to time.ts['st']

dateString timePattern translator Exactly like the bracket operator(s) of time.ts['tai'], except supports time-zones. If no time zone keyword is used to parse the string (tz, TZN, tzo, or tzom), UTC is assumed. If no pattern is passed in, the default pattern is
time.p[(y('/'m$mp('/'d$dp))) (h$hp':'min(':'sr)(' 'tz))].
time.ts['st']["2012/06/04 15:00"] ; June 4th, 2012 at 3:00pm UTC time.ts['st']["2012/06/04 15:00 America/Los_Angeles"] ; June 4th, 2012 at 3:00pm PST time.ts['st']["20120604" time.p[ympdp]] ; June 4th, 2012 (at 0:00, ie 12:00am) UTC time.ts['st'][2012/06/04 15:00] ; June 4th, 2012 at 3:00pm UTC time.ts['st'][3:00] ; 3 hours in to the common era (usually the meaning is as a duration of 3 hours)

Time pattern keywords available to time.ts['st']

Has all the time-pattern keywords that time.ts['tai'] has, with the following additions:
tz Time zone as a local time-zone id (eg. "America/Los_Angeles").
TZN Time zone as a name (e.g. "Pacific Standard Time").
tzo Time zone offset - whole number of hours offset from UTC, including sign (e.g. "-3" in "April 3 2401 8:00 Z-3:30").
tzop Time zone offset, padded. (e.g. "-03" in "April 3 2401 8:00 Z-03:30").
tzom Time zone offset minutes - whole number absolute value of minutes offset from UTC (e.g. "30" in "April 3 2401 8:00 Z-3:30").
Note about time zones: time-zone abbreviations are non-standard and in some cases ambiguous, so Lima doesn't support them.

UTC members of time-objects: timeObject.ts['st']

Has all the members timeObject.ts['tai'] has with the following addition:
TZN Time zone as a name.

Has all the methods timeObject.ts['tai'] has. Note that any translater used should also be able to translate time zone names and abbreviations.

Has the following methods:

format timePattern timezone translator Just like the format methods for tai time-objects (timeObject.tai), except a timezone can be passed. If no timeZone is passed in, the default is UTC.

utc ut0 ut1 ut1r ut2 ut2r Versions of Universal Time. These have all the same methods, operators, and time-pattern keywords time.ts['tai'] has.
GPS Global Positioning System Time
TT Terrestrial Time
TDB Barycentric Dynamical Time - a relativistic dynamical time.
UNIX Unix timestamp, the number of UTC seconds since Jan 1, 1970 00:00 UTC.

Methods and operators available to time.ts['unix']

unixTimestamp Returns a time object representing the time indicated by unixTimestamp. unixTimestamp must be a number (can be fractional).
var x = int.parse[915148801.25] time.ts['unix'][x] ; 1999/01/01 00:00:33.25 TAI time.ts['unix'][1095379200] ; 2004/09/17 00:00:32 TAI

Members available to timeObject.ts['unix'] for a given time-object

stamp Returns the unix timestamp as a number.
var t = time.ts['unix'][1095379200.54] ; 2004/09/17 00:00:32.54 TAI t.ts['unix'].stamp ; returns 1095379200.54
s The integer timestamp rounded to the nearest second (standard unix timestamp).
var t = time.ts['unix'][1095379200.54] ; 2004/09/17 00:00:32.54 TAI t.unix.s ; returns 1095379200
ms The integer timestamp rounded to the nearest millisecond.
Note that when dealing with a date format with ambiguous dates for a particular time-standard, the earliest possible time is assumed to be the one meant.

dur An object that has methods for representing and operating on durations. Uses a constant definition for date parts:
  • y: year = 365.25 days (A Julian Year) or 12 months
  • m: month = 30.4375 days (Based on the Julian Year)
  • week = 7 days (168 hours)
  • d: day = 24 hours (86,400 seconds)
  • h: hour = 60 minutes
  • min: minute = 60 seconds
  • s: second = an SI second
Methods:
Returns a type that enforces variables to hold dur objects.
dur! t = dur[3:00]
duractionText Returns a dur object of the duractionText parsed as a duration. The pattern it parses off of is
time.p[(y'/')$((y)'/'m('/'d))$('/'d) s$((h)':'min':'(s))$(h':'(min))].
dur[15] ; 15 seconds dur[15.45] ; 15.45 seconds dur[150] ; 150 seconds dur[:15:0] ; 15 minutes dur[:15:] ; 15 minutes dur[15:0] ; 15 hours dur[-7:00] ; negative 7 hours dur[0:30.5] ; 30 minutes and a half (30 minutes and 30 seconds) dur[:30:29.56] ; 30 minutes, 29 seconds, and 560 milliseconds dur[/5/1] ; 5 months and 1 day dur[23/9] ; 23 years and 9 months dur[10/] ; 10 years dur[-23/10/1] ; negative 23 years, 10 months, and 1 day dur[5/1] ; 5 years and 1 month dur[2/8/23 3:00:00.05] ; 2 years, 8 months, 23 days, 3 hours, and 50 milliseconds

dur["00"] is a special case indicating an infinite amount of time. Note that dur[00].time == time[00].

duractionString Like the bracket operator above, but instead of writing literal duractionText, a string is passed instead. Uses the same pattern from the bracket operator with the duractionText parameter. If the string can't be parsed, an exception is thrown.
dur["15:00"] dur[":30:29.56"] dur["-23/10/1"] ; same as dur[-23/10/1]

Duration objects

Duration objects represent a length of time.

dur objects have the following members:
str The to-string member. The pattern output is: time.p[y'/'m'/'d' 'h':'min':'sr]
dur objects have the following methods:
timePatternKeywords Returns an object that counts the time only in terms of the parts given in the timePatternKeywords. timePatternKeywords are similar to the input into time.p. The resulting object will have members (of the same name as the time pattern part) representing the amount of time where, all added up, they equal the original duration. The smallest time unit can have a fractional value, all others will be integers.
dur[15:00][d h s] ; returns {d=0 h=15 s=0} dur[34 4:34:23.4][h min] ; returns {h=820 min=34.39} var pattern = time.p[y'-'m'-'d] dur[3/4/20][@pattern] ; you can use time.p objects like that: returns {y=3 m=4 d=20}
y The number of years.
m The number of months.
w The number of weeks.
d The number of days.
h The number of hours.
min The number of minutes.
s The number of seconds.
dur objects have the following binary operators:
a + b Addition of durations, or addition of a time and a duration.
time[2012.04.05 20:00] + dur[3:00] ; returns an object equal to time[2012/04/05 23:00] dur[3:00] + dur[..24] ; returns a dur object representing 24 days and 3 hours
a - b Subtraction of a duration.
time[2012.04.05 20:00] - dur[3:00] ; returns an object equal to time[2012/04/05 17:00] dur[..24] - dur[3:00] ; returns a dur object representing 23 days and 21 hours
a * b Duration multiplication. Multiplies a duration. One of the operands must be a dur object, and the other must be a number.
3*dur[2:20] ; returns an object equal to dur[7:00]
a / b Duration division. Divides a duration. The first operand must be a dur object and the second must be a number.
dur[1:00]/4 ; returns an object equal to time[0:25]
a < b Less than comparison between durations.
dur[1:00] < dur[4:00] ; returns true
a <= b Less than or equal to comparison between durations.
a > b Greater than comparison between durations.
dur[1:00] > dur[4:00] ; returns false
a >= b Greater than or equal to comparison between durations.
time objects have the following unary operators:
-a Negative. Returns the negative of the duration.
-dur[1:00] ; returns negative 1 hour var x = dur[-2:00] ; negative 2 hours -x ; 2 hours
Object containing methods for dealing with files. The behavior of this object can be overridden using the matr.fileObject module attribute.
Methods:
filePath

Opens the file that has the given path. Creates the file if it doesn't exist and creates any directories needed for the path. filePath is a platform-independant path string in the style of unix filepaths. The bracket-operator will concatenate any path parts passed. It is opened for reading and/or writing (depending on how the program will use the file).

Since filePath is in unix-style, the folder separator is '/'. The root folder is '/' and paths that don't begin with a '/' character are relative paths. Also, '..' represents the previous directory. For example, the windows path "C:\someFolder\someFile.txt" would be represented as "/C/someFolder/someFile.txt" in Lima. In a system without a root, like windows, '/' can be represented, but can't have anything written to it in the normal way (you'd have to mount a new drive to add something at the root). Filepaths must be transcodable to the encoding dir.enc, and if not, an exception will be thrown.

Returns a file! object representing the bits inside the file (bits representing the raw bytes, without OS specific conversions). The file can be written to as if it was a and has all the methods and operators a normal bits-object has. Can throw some errors, including: "unable to write", and "unable to overwrite", and "unable to open".

The returned object contains the following pseudo-members:
name Holds the file's name.
dir Holds a dir object representing the directory the file is in.
type Holds the type of file this object represents. The values this can take on is filesystem dependent, but will generally be one of the following:
  • file
  • blockDevice
  • characterDevice
  • symLink
  • fifo
  • socket
close This pseudomember closes the file. Attempting to read from or write to the file will reopen it.
rm This deletes the file.
flush This flushes the buffer for the file. Should only be used if something absolutely needs that file to be updated immediately.
stats Contains filesystem specific information about the file. An example of stats info for a file in a usual linux file-system:
{ dev= 2114, ino= 48064969, mode= 33188, nlink= 1, uid= 85, gid= 100, rdev= 0, size= 527, blksize= 4096, blocks= 8, atime= time[2011-08-10 23:24:11], mtime= time[2011-08-10 23:24:11], ctime= time[2011-08-10 23:24:11] }
meta

This is a associative array that allows access to the file's metadata.

If the filesystem doesn't natively support arbitrary metadata, lima will create a hidden file in the same directory named ".metadata" and store the metadata there in LiON format encoded in UTF-8. The top level value will be an object that contains the metadata keyed with the name of each file or directory within the directory the ".metadata" is in. The value at each file/directory key will be an object containing the metadata.

filesystem Returns the filesystem in which the file resides. Has the same members and methods that the filesystem member of a dir object has.
The returned object contains the following methods:
move stringPath Renames/moves the file to the new path and name.
unmake The file destructor flushes and closes the file.
dec encoding Returns a similar file object, except that it contains string characters instead of bits. encoding should be the string-name of the encoding. If encoding is not passed, this will use the string at metadata key "fileEncoding" as the encoding, if available. If not available for whatever reason, an exception will be thrown.
filePath encoding Returns a string-file obtained by decoding the file. This is the same thing as:
; var x = file[path encoding] var x = file[path].dec[encoding] x.meta["fileEncoding"] = encoding
This construction ensures that the file will record its encoding for later use.
Returns a type that can only take on a file - an object returned by this object's bracket operator described above.
Object with methods for dealing with directories in the filesystem. The behavior of this object can be overridden using the matr.dirObject module attribute. Methods:
pathString Returns a directory literal. If the directory does not yet exist, it does not create the directory, and the object's methods treat it as if it was an existant, but empty, directory. pathString contains the directory path. pathString should have the same system-independant unix-style path that file takes as a file path. Directory paths must be transcodable to the encoding dir.enc, and if not, an exception will be thrown.
The returned object contains the following pseudo-members:
path Returns the system-independant path string of the directory. Always contains a '/' at the end.
make Creates the directory and any directories it needs to create its path. If a directory already exists, nothing will be done. If something exists with that name that is not a directory, an exception will be thrown.
dirs A list of child directories keyed by their names.
files A list of files in the directory keyed by their names. This list includes any file-system entity that isn't a directory.
up Returns the parent directory if the directory object isn't the root. Returns nil if the directory object is the root.
rm Deletes the current directory and any containing files and folders.
str Its to-string method, prints the directory path.
filesystem

Returns an object that has information about the filesystem and functions for converting to and from filesystem-specific values.

It has the following members:

enc The encoding used by the filesystem (how it stores filenames, etc).

It has the following methods:

toNativePath limaPath Returns a native path for this filesystem from the given system-independent path.
fromNativePath fileSystemPath Returns a system-independent path given the given filesystem path.

The returned object contains the following methods:
move stringPath Renames/moves the directory and all of its contents to the new relative or absolute path. Will throw an exception if something already exists at that path.
Members and unary operators:
Returns a type which constrains an object to only take on a directory.
cwd A object representing the current working directory of the program. Modifying this modifies the cwd of the process.
main A object representing the directory the executable lives in.
module Returns a object representing the directory the module lives in.
adr

Handles facilities for IP addresses and ports. Has the following methods.

addressFormat Macro operator that creates an address including IP-address, port, and scope. Takes in a few different formats that it detects automatically:
  • IPv4 address or IPv6 address.
  • Address plus port.
  • Address plus port.
  • Address, port, and scope id (only for ipv6 addresses, defaults to global scope if not given)
  • String of one of the previous three formats
  • adr object plus port (errors if adr object already has a port)
  • string ip address plus port (errors if adr object already has a port)
  • adr object, port, and scope (errors if adr object already has a port or scope)
var address = adr[129.34.0.50] var fullAddress = adr[53.0.0.40:80] var ipv6Address = adr[2001:db8:85a3:8d3:1319:8a2e:370:7348] var fullIpv6Address = adr[[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443] var s1 = "129.34.0.50" ; ipv4 address var s2 = "128.0.0.1:8080" ; ipv4 address plus port var s3 = "2001:db8:85a3:8d3:1319:8a2e:370:7348" ; ipv6 address var s4 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]" ; ipv6 address (with brackets) var s5 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443" ; ipv6 address and port var s6 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348%3]:443" ; ipv6 address, scope, and port var ipaddress = adr[s1] var ipaddressWithPort = adr[s2] var ipv6Address = adr[s3] var ipv6AddressToo = adr[s4] var ipv6AddressWithPort = adr[s5] var ipv6AddressWithPort = adr[s6] var addrAndPort = adr[ipaddress 5000] var addrAndPort = adr["129.34.0.50" 5000] var addrAndPortAndScope = adr[ipaddress 5001 3]
adr! The unary exclamation mark returns a type that can only take on an IP address object.
4 number port Returns an adr object containing the IPv4 address number represents.
var ipv4Addr = adr.4[500049000] var ipv4Addr2 = adr.4[500049000 4000]
6 number port scope Returns an adr object containing the IPv6 address number represents.
var ipv6Addr = adr.6[3000298888837700 6000 3]

Also has the following pseudo-member:
own A list of adr! objects each containing the address for a network adapter.
interfaces An object where each key is the name of the network interface/adapter (human readable, not necessarily standard), and each value is an adr! object representing the address for that named network adapter. The values can be compared for equality to the elements in adr.own.

The object returned by the bracket-operator has the following members:

type Returns the name of the address family (usually 'ipv4' or 'ipv6').
str Returns the string form of the address (eg. "128.0.0.1:8080").
num Returns the address as an integer.
port The port number (nil if not set).
scope The scope value. Throws an exception if not an ipv6 address.

udp

Object for handling UDP packets. The behavior of this object can be overridden using the matr.udpObject module attribute. Has the following methods:

make destinationAdr deviceAdr Initializes and returns a UDP packet stream with the given destinationAdr (an adr! object). The deviceAdr parameter is an adr representing the IP of the device to send and listen from and is optional (defaulting to adr.own[0]). Returns an extended chan object where appending to the out property will send to the (destination) address and port given.
packet sourceAdr destAdr dataByteList Creates a UDP packet. This isn't needed for normal UDP handling (as packets are constructed automatically), but it can be useful if creating packets for direct socket use.
packet bitList Unserializes a bits bit-list object into a udp packet.

UDP sockets
UDP socket objects are returned by udp's constructor and inherit from chan. UDP sockets have the following additional method:
listen port Starts listening on the port passed in the sourceAdr parameter of the constructor, giving the program an exclusive lock on that port. Returns the calling object, for convenience.
UDP sockets also have the following additional members:
in Contains a list of all the UDP packets that will arrived on the udp socket from the time listen was called to the time the udp port is closed.
out Appending a packet to this list sends that packet out on the socket. Strings, bits, or UDP packets can be appended to out. The UDP packet's source and destination will be used, if different from the UDP socket's.
source An object containing an adr! object representing the source address - the address from which packets will be sent. Holds nil if not listening.
dest An object containing a list of adr! objects representing the addresses to which messages will be sent (regardless of which address messages are received from, if the object is listening on a port). Holds an empty list if not initialized.
open Contains true if listening, false if closed.
close Pseudo-member that causes the object to stop listening to the port, and returns the lock on that port. Note that a properly running Lima runtime will properly close all connections even if the program itself is forced to quit by an unexpected internal failure.

Note: Copies of udp, tcp, http, and raw socket objects that were listening/connected when copied are also listening/connected.

UDP packets

The UDP packet objects returned by udp's constructor and read from the out list of a UDP socket have the following members and methods:

source An object containing an adr! object representing the source address.
dest An object containing an adr! object representing the address the packet was addressed to.
bits Contains the packet's bits serialization.

var udpSocket = udp[adr[34.8.0.1 8080]].listen[8080] var message = udpSocket.in.=rm[0] ; gets the first packet if message == 'hi': udpSocket.out.ins['hi 'message.source] ; send message back else: udpSocket.out.=ins["Outta my way!"]
tcp

Object for handling TCP connections. The behavior of this object can be overridden using the matr.tcpObject module attribute. Has the following methods:

listen port encodingName connections deviceAddress

Listens for connections on the given code. encodingName is required and defines how to decode incoming bytes and encode outgoing bytes. connections is optional. If connections isn't given, listen will connect to the first incomming computer and return a single tcp stream object. If connections is given, listen connects to that number of connections and returns a list of those connections. deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]).

Lazily executes - doesn't block execution. Execution won't be blocked until the program needs information it is waiting for. Note that this means your program can use and process the first connection, even if listen is still listening for more connections.

connect address encodingName sourcePort deviceAddress Attempts to connect to an address from the sourcePort. encodingName is required and defines how to decode incoming bytes and encode outgoing bytes. sourcePort is optional and if not given, an arbitrary free port (ephemeral port) will be used - basically a port tcp.ports[arb[0..ports.len]]. deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]). Returns an open connection.
packet source dest seq data ackNumber flags windowSize urgentPointer options Creates a TCP packet. This isn't needed for normal TCP handling (as packets are constructed automatically), but it can be useful if creating packets for direct socket use.

TCP sockets
The objects returned by tcp.listen and tcp.connect have the following members:
in A sequence (or bits object, depending on the encoding) of characters that will arrive on the TCP socket from the time the connection was established to the time it will close.
out Appending strings to this list sends the characters of that string out on the TCP socket. Strings or bits can be appended to out.
open Contains true if the connection is open, false if closed.
close Pseudo-member that closes the connection. Does nothing if connection is already closed.
source An object containing an adr! object representing the source address.
dest An object containing an adr! object representing the destination address.

var connection = tcp.listen[8080] ; connects to another computer var messages = connection.in.split[''@] ; messages delimited by newlines if messages[0] == 'hi': connection.out.=cat['hi'] ; send a message back else: logger.i[messages[0]] ; print message
inputs

A list of objects representing the state of input devices like keyboards and controllers. Each object in the list of inputs will have a number of keys, and each value at a key represents the state of that key at a given time. Many keys only have two states, and will then usually be represented as true or false. Some other keys represent inputs with multiple states, like dials or scrollers, and will usually have some number representing its state.

Note that the state of inputs are accessible regardless of whether the window is on top or in focus.

The behavior of this object can be overridden using the matr.inputsObject module attribute.

Inputs are intended to be used with event semantics:

change[inputs[0].a]: if inputs[0].a: ; keydown else: ; keyup

Like any input object, this can have more or less members depending on the device. The members representing button states can be written to programatically for any device. This value remains valid until the real device changes to a different state (note that if the real device changes to the state the device was already changed to programatically, it is as if the change never happened).

keyboard

An object representing the state of the keyboard.

The members of the object are named after the keys they attach to.

if keyboard.f: logger.i['f is down'] if keyboard['=']: logger.i['equals is down'] ; non-letter keys are accessed as string keys if keyboard.f3: logger.i['f3 is down'] if keyboard.shift && keyboard.3: logger.i['shift is down *and* 3 is down, is # down?'] keyboard['$'] ; always false for most keyboards, since there is no dedicated $ key (its a different state of the 4 key)
Keyboards also have a combo sub-object (which is an empty input object if a keyboard doesn't exist). These differentiate between keyboard modes that change when certain keys (like shift, fn, caps lock, ctrl, or alt) are activated or held down, if the keyboard has specific meanings for combinations of state.
if keyboard.combo['#'] && ! keyboard.combo['3']: logger.i['# is down, but 3 is not'] keyboard.combo['#'] && ! keyboard['3']: ; for most keyboards, this is always false, because 3 and # are the same key

The keyboard object always has the following member:

active This is true if a keyboard exists, false if not. This member is read-only.

This object always exists, even if there is no mouse or keyboard. The touch and keyboard objects are also in the list of inputs, but this is shorthand.

Keyboard and touch objects are typed such that all members are allowed to be nil. This way, if an input button doesn't exist, any code can still access the button member and will not throw exceptions - likely simply ignoring the related code since the state will never change. This promotes cross-system compatibility and graceful failure.

touch

A list object representing the state of the mouse, mice, and/or touch-events - any device that has a position on the screen.

The list object itself has the following member:

max The maximum number of cursors it supports. If this number is 0, it means there is no mouse/touch device.

The objects the list holds may have the following true/false members:

p A shape object containing the point at which the mouse/touch cursor is on the screen.
pri State of the primary mouse button. Usually points to either L or R (usually L), but may also be its own member in touch-screen cases (which don't have L or R).
sec State of the secondary mouse button. Usually points to either L or R (usually R).
L State of the left mouse button.
R State of the right mouse button.
M State of the middle mouse button.
The objects the list holds can have the following integer member:
scroll State of the scroller.

when touch.any.p < p[{0 0}{0 10}{10 0}{10 10}]: ; do something when any mouse cursor hovers over the given square area

dns Object containing methods for dns address lookup. Note that this ignores any system-defined names (/etc/hosts and the like), so if you want those to be included, use system.host. The behavior of this object can be overridden using the matr.dnsObject module attribute. Has the following methods:
[] name type class edns=false deviceAddress=adr.own[0]

Returns the dns answer to the query. name is the name of the resource you want to query about (usually either a hostname or an ip address). type is the the dns rrtype ('A', 'AAAA', 'MX', 'TXT', 'SRV', 'PTR', 'NS', 'CNAME', 'SOA', 'NAPTR', etc). class is the numerical representation of the class of service (usually 1 for internet). deviceAddress (optional) is the address of the device to query from.

The answer is an object that looks like:

{ header= { qr=<query response> opcode=___ aa=<authoratative answer> tc=<truncation bit> rd=<recursion desired> ra=<recursion available> rcode=<response code> } answer=___ authority=___ additional=___ }
where answer, authority, and additional are lists of resource record objects that look like this:
{ type=<dns rrtype> ttl=___ ... ; additional fields }
The additional fields in resource record objects are the following (by type)
type properties
SOA
  • primary
  • admin
  • serial
  • refresh
  • retry
  • expiration
  • minimum
A and AAAA
  • address - an address (adr! objects) for the host hostString.
MX
  • priority
  • exchange - the hostname of the mail exchange
TXT
  • data - the string text record
SRV
  • priority
  • weight
  • port
  • target
NS
  • data - a name server hostname
CNAME
  • data - a canonical name record
PTR
  • data
NAPTR
  • order
  • preference
  • flags
  • service
  • regexp
  • replacement

ips string hostString adr! deviceAddress Resolves a domain name to a list of IP addresses. Returns a list of adr! objects with the addresses. deviceAddress (optional) is the interface do request from. This is the same thing as:
dns[hostString 'A' 1 deviceAddress].map[v.answer] & dns[hostString 'AAAA' 1 deviceAddress].map[v.answer]
hosts adr! address adr! deviceAddress Reverse resolves an ip address to a list of domain names. Returns a list of hostnames corresponding to that address. deviceAddress is optional. This is the same thing as:
dns[address.str 'PTR' 1].map[v.answer]
alternate adr! address string type Returns an object that also has the ips and hosts, and can also be called as a function, but calls the dns server specified by address with the protocol specified by type ('udp' or 'tcp'), rather than your router's default dns server.
servers A member that returns the list of DNS servers the operating system uses.
socket Object for handling raw internet packets. The behavior of this object can be overridden using the matr.socketObject module attribute. Has the following method:
make deviceAddress

Initializes and returns a raw packet stream with the given destination. Returns a chan object that receives IP packets of any version. deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]). Outputting on out sends a packet, and new packets will come in on in (if its listening). The in property always has the encoding "bits". On call of close the socket will be closed.

Objects returned by socket's constructor have the following method:
listen

A pseudo-method that starts listening to the socket. Can restart listening on a socket that was previously closed. Returns the calling object (for chaining).

; processes all the packets that come through (infinite loop) var server = socket[].listen df server.in packet: if packet.version == 4: ; ipv4 var p = ipv4[packet.data] var ipv4Packet = ipv4[ ttl = 30 protocol = 17 destination = p.source data = udp[dest=8000 data="hi"].serialize ] server.out.=ins[ipv4Packet] ; sends the IPv4 packet with a UDP message

ipv4 IPv4 packet object. The behavior of this object can be overridden using the matr.ipv4Object module attribute. Has the following methods:
make ttl protocol destination data

Creates an ipv4 packet.

make bitList

Creates an ipv4 packet from bits.

And has the following members:

version The IP protocol version (4 for this).
ttl Time to live.
protocol Transport layer protocol number.
destination An object containing an adr! object representing the address the packet was addressed to.
bits Contains the packet's bits serialization.

ipv6 Just like IPv4 except with IPv6 members instead. The behavior of this object can be overridden using the matr.ipv6Object module attribute.
url

An object for parsing, creating, and handling URLs. Quick reference: protocol:[//[user:pass@]host[:port]][/]path[?query.str][#frag]

url has the following methods:

make urlString This constructor parses the urlString and returns a url object that represents it.
make options Returns a url object that has the passed properties. The properties can be any of protocol, host, port, path, query, frag, fullHost, or fullPath (host and port are mutually exlucive with fullHost, and path and query are mutually exclusive with fullPath).
encode string URL-encode a string.
decode string URL-decode a string.
queryStr objectMap Returns a URL encoded query string that represents the object map. Throws an exception if the object can't be represented by a query string.

url has the following member:

defaults An object containing info on what port is default for a protocol. Each key is a protocol, and each value is a port number.
url objects

url objects have the following members:

protocol
user The username part of the URL.
pass
host
port
path
query An object where each key is a url query property, and each value is its value (either a string or a list of strings).

Also has the member:

str Returns the url encoded query string. Setting this variable changes the properties set in the urlObject.query object to match.
frag Fragment identifier (the part after the # hash mark).
fullhost host.cat[':'port]
fullPath path.cat[':'query.str]
str Returns the url as a string. If the port is originally specified but is known to be the default (it checks url.defaults for this), the port is known to be default for that protocol, this omits the port in the string even if the port was originally specified. Protocol and host is always converted to lower case.
The members protocol, host, port, path, query, frag, fullHost, or fullPath are all mutable so you can change the URL at will.
http

Object for handling HTTP requests. The behavior of this object can be overridden using the matr.httpObject module attribute. Has the following methods:

listen port encodingName deviceAddress

Listens for incomming http requests. encodingName is optional, and defines how to decode the HTTP request and encode the response. If encodingName is not defined, Lima will attempt to get the encoding from the request's "Content-Type" header, using "bits" if none is found. deviceAddress is optional. Returns a list containing all the HTTP requests that will arrive on the HTTP socket from the time listen was called to when the socket is closed.

Note that if you stop processing a request or close its connection before reading the entire body or before reading request headers (etc), Lima should optimize that such that the connection will be dropped or closed as soon as possible (potentially without loading anything but the status code).

make host method resource query headers body deviceAddress Creates an http request object. deviceAddress is optional. query should be an object that will be built into a query string.
cookie name value expire Creates a cookie. The following members can also be explicitly set: domain, maxAge, path, secure, httponly. Note that maxAge will delete the 'expire' member if set (and vice versa).
var listener = http.listen[8080] ; listens for connections df listener.in request: request.respond[503 nil "we're not on speaking terms"]

HTTP request objects
The objects returned by http's constructor and http.listen have the following members:
method The http method.
resource The requested resource.
version The http version (eg. 1.1)
source An object containing an adr! object representing the source address. This is only populated on the computer receiving the request.
open Contains true if the connection is open, false if closed.
close Pseudo-member that closes the connection. Does nothing if connection is already closed.
query The query string. Also has the member:
map The query parameter map (from parsing the query string). Throws an exception if not able to parse the query string.
httpRequest.query.map.userId[0] ; get the first userid parameter in the query string
headers A list of header values keyed by their name.
body The http body (as a string). Also has the following members:
map The http body parsed as a parameter map ("post parameters"). Attempting to access map throws an exception if the body is not able to parsed as a query string.
str Returns the request as a string. This can be used to send the packet out through a TCP socket.

HTTP request objects also have the following methods:

respond status headers response keepalive Responds to the request. Closes the connection after the request is sent unless keepalive is passed in as true. Note that keepalive will keep the connection open forever until explicitly closed by one side or the other.
send address sourcePort deviceAddress Sends the HTTP request to the given address and returns an HTTP response. address can either be an adr! object, or it can be a string host. If address is a string, the host header will automatically be set if it hasn't been already.

HTTP response objects
HTTP response object have the following members:
status The http method.
version The http version (eg. 1.1)
open Contains true if the connection is open, false if closed.
close Pseudo-member that closes the connection. Does nothing if connection is already closed.
headers A list of header values keyed by their name.
body The http body (as a string). Also has the following members:
map The http body parsed as a parameter map ("post parameters"). Attempting to access map throws an exception if the body is not able to parsed as a query string.
str Returns the response as a string. This can be used to send the packet out through a TCP socket.
https

Object for handling HTTPS requests. Has the same interface as http. The behavior of this object can be overridden using the matr.httpsObject module attribute. HTTPS request objects also have the following member:

auth Returns the auth scheme used.

lion

Object for parsing and outputting LiON notation strings.

[value] Returns a string representing the passed value. Only outputs normal properties of the object (not privileged members).
lion[5] ; outputs "5" lion[{1 2 3}] ; outputs "{1 2 3}" lion[{'a':3 'b':{'d':4}}] ; outputs "{a=3 b={d=4}}"
parse[string] Returns the value represented by the LiON string.
lion.parse["5"] ; outputs 5 lion.parse["{1 2 3}"] ; outputs {1 2 3} lion.parse["{a=3 b={d=4} c=~{"b" "d"}}"] ; outputs {'a':3 'b':{'d':4}}

parse

Object containing methods for parsing arbitrary strings as well as lima source code strings.

fn codeString paramList context Parses the codeString into a function immediate. The context (optional) is an object containing the following optional members:
  1. scope - An object where its members are variables in scope.
  2. this - An object that 'this' will reference.
  3. atr - An object where each property is a context attribute that will be available.
  4. matr - An object where each property is a module attribute that will be available.
The paramList (optional) is a list of parameter names the returned function will take.
mut i = 5 var theString = 'i++ ret x-1' fn! f = parse.fn[theString {'x'} {scope:{i~>i}}] f[8] ; i becomes 6 and returns 7 fn! f2 = parse.fn[theString {'x'} {}] f[10] ; throws an exception because i is undeclared var y=0 fn! f2 = parse.fn[theString {'x'} {scope:{i~>y}}] f[99] ; y becomes 1 and returns 98
parse.fn and parse.var greatly increase safety of running external code over the normal exec function in most programing languages (which executes statements from the string inline). In Lima, these functions don't allow any variables to implicitly leak into the evaluated code - the includes attributes.
parse codeString context Parses the codeString and executes the resulting lima code to build an object.
var o1 = parse.var['1 2 3'] o1[0] ; returns 1 var i = 5 var theString = 'a = 5 b = i i = 9 c = fn: logger.i[this.i]' var obj = parse.var[theString {scope:{i=i}}] obj.a ; 5 obj.b ; 5 obj.i ; 9 obj.c[] ; prints 9 var x = 5 var codeString = "p~>a f = fn: p++" var obj2 = parse.var[codeString {scope:{a~>x}}] obj2.f[] ; x becomes 6
round value valueSet towardValue Returns the value rounded toward towardValue in the set valueSet. valueSet is optional (defaulted to the set of integers - since numbers are what are usually rounded). towardValue is optional, without it rounds to the nearest value in valueSet, with it rounds toward that towardValue. Throws an exception if the value is not comparable with items in valueSet.

; all of these are true: ; numbers round[34.245] == 34 round[34.5] == 35 round[34.8 int.set.map[v*.5]] == 35 round[34.7 int.set.map[v*.5]] == 34.5 round[34.7 int.set 0] == 34 round[34.7 int.set.map[[v*5]] 0] == 30 round[-34.7 int.set 0] == -34 round[34.7 int.set 0] == 34 round[34.7 int.set 00] == 35 round[34.7 int.set.map[[v*6]] 00] == 36 ; 6*6 == 36 and 6*5 == 30 (so 36 is closer) round[-34.7 int.set -00] == -35 round[19.8 int.set 15] == 19 round[14.8 int.set 15] == 15] ; time var t = time[2012-04-03 12:00] ; rounds to the nearest week (weeks here defined as starting on monday) round[t time.tai.weeks] == time[2012-04-02] ; rounds to the nearest wednesday round[t time.tai.weeks.map[v+dur[/0/2]]] == time[2012-04-04] t.tai.round[t time.utc.years] == time[2012-01-01] ; midday counts as nearest to the next day (like .5 being rounded as closest to 1) t.tai.round[t time.utc.days] == time[2012-04-04] ; rounds to the nearest day that is the 9th of a month t.tai.round[t time.utc.months.map[v+dur[/0/9]]] == time[2012-04-09] ; rounds to the nearest 1/3 of a day (again rounding up in this case) t.tai.round[t time.utc.days.map[v - dur[/1]/3]] == time[2012-04-05 16:00] ; rounds toward time 0 - in other words time[1/] or time[0001/01/01 00:00:00] t.tai.round[t time.months dur.0.time] == time[2012-04-01] ; rounds toward time infinity (rounds up) t.tai.round[t time.months dur.00.time] == time[2012-05-01]

mag value Returns the magnitude of a value. The value can be any number (int, real, complex, etc), or vector of numbers. It should be noted that this function is equivalent to the absolute value when used on non-vector numbers.
sir radians Sine for radians
cor radians Cosine for radians
deg Converts degrees to radians. For example, [90] would be /2 (radians).
Gets the sign of a non-complex number. Returns either 1 or -1.
.base value Gets the logarithm base `base` of the passed value.
cat value1 value2 ... Shorthand for value1.cat[value2 value3 ...]
toCodeString value Outputs a Lima code string representing that value.
access get set

Returns an accessor object. The parameter get is the getter function for the value, and set is the setter function. All other operations on the object are performed on the result of the getter function.

This is useful for defining getters and setters for an object.
var o = { internalX = 5 x = access[ get=fn: return internalX set=fn v: internalX = v ] }
load module type

Loads a resource from a library. Usually this is loading a module (as a static or dynamic library). module can either be a module name, a module-path (explained below) to the file containing the module-file, or a module-path with a version (eg load['someModule@1.0.1/some/internal/module']. type is optional (default='module') and describes whether to load the resource as a 'file' or as a 'module'. If type is "file", it will return a file! object containing the file. Otherwise, load will return a copy of the object representing the module.

A module-path is similar to how node.js require takes paths. Module resolution is done as follows:

  1. If the path starts with "./" or "../", the file is looked for at the given path relative to the loading module
  2. If the path starts with "/", the file is looked for at the given absolute path
  3. Otherwise, a "modules" folder is looked for in the directory containing the loading module, and
    1. If found, the file is looked for at the path relative to that "modules" folder.
    2. If not found, step C is repeated for the parent directory
    3. If still not found, step C.1 is repeated until the current directory is the directory the package.lima file is in
    4. If it still isn't found, the first part of the path is looked for in the "dependencies" list in the "package.lima" file, and searched for in the user's lima module installation directory.
      1. If found and the module-path is just the module's name, the file at the path listed in the property "main" in that module's "package.lima" file, is loaded
      2. If found and the module-path is more than just the module's name, the file at that path relative to that module's folder, is loaded
      3. If not found in the user's module installation directory and the name is listed in the package.lima file, the program attempts to download the module and install it and its dependencies in the user's module installation directory.
  4. If the module isn't found in any of the above ways, the path is appended with '.lima' and steps are tried again.
  5. If the module still isn't found, load throws an exception

var websocket = load['websocket'] websocket.connect['ws://localhost'] ; calls the connect function from that library ; load non-lima modules var crypto = load['./crypto-v1.0.0.lib'] ; loads crypto methods in the current scope crypto.md5['something'] ; hypothetically in the crypto library mix['customLibrary'] // you can also mix the module object directly into the current module customLibraryFunction[45 'do something!'] ; hypothetically from the customLibrary module ; you can also specify a version var markdownHelper = load['markdown@2.0.0/dist/helper'] websocketHelper.parse['### hi']

Examples of types of packages you would use with this: lima source files (uncompiled libraries), compiled static libraries, shared libraries (DLLs, so files), executables (loads it like a shared library). If one of the filenames a string ending in ".lima", ".o", ".dll", ".so", etc, it loads the code at the given path and uses the entire file as an object.

"package.lima" is a file, much like node.js's "package.json", containing information about the current package, and the dependencies of that package. It can have the following properties (unless marked, a property is optional)

  • name - (required) Must be a valid variable name
  • version - (required)
  • main - (required) The path (relative to the directory the package.lima file is in) to the main module file for this package.
  • description
  • keywords
  • recommended - a list of optimization packages that will likely work well with the module.
  • homepage
  • bugs
  • repository
  • license
  • author
  • contributors
  • errorChecking - Set to "strong" if you want the compiler to fail when it finds at least one `StrongError` that could potentially be thrown. If `errorChecking` is set to "weak" or omitted, the compiler will only fail when it can determine that at least one exception (`StrongError` or not) will *always* happen. In the future, this might have a form that allows per-file errorChecking configuration.
  • dependencies - a list/object of modules a package depends on. Each item in the list/object is one of the following:
    • <name>@<version> - downloads the specified <version> of the package <name> from lime (lima's package manager - like ruby gems, perl's cpan, or node's npm). <version> should be a semvar version.
    • <name>: <url> - downloads the package <name> from the given url. It expects the downloaded file to be an archived tarball.
    • <name> - Downloads the latest version of the package <name> from lime. After downloading, the package.lima file will be updated with the specific version downloaded (turning it into the <name>@<version> format).
  • ignore - files and folder to ignore when publishing the package
  • preventPublishing - If true, publishing the package will be prevented whenever possible.
  • postinstall - a list of dependencies, how to check if they're installed, and how to install them if they're not.

    Each item in the list should have the following format:

    { name = name ; name of the dependency check = checkFunction ; returns true if the dependencies it is responsible for are met build = buildFunction ; builds a dependency }
    If a build member isn't given for a particular dependency, it indicates that the dependency can't be met automatically. If a dependency isn't met and can't be met automatically, an exception will be thrown containing a list of the names of the dependencies that can't be fulfilled.

  • location - a path to where external modules should be installed to. Defaults to a standardized location depending on the operating system the program is running on.

Since "package.lima" is a full-fledge lima program, you can programatically define any of the above fields.

dif originalObject newObject A function that compares the non-privileged members of two objects and returns the difference between them. The return value is an object where each key is a property-path and each value describes the change. The property-path is a list where [] indicates the object itself, ['a'] indicates object.a, etc. Each value has one of the following forms:
{type='set'  val=value } This indicates that the property was set to a new value.
{type='move'  from=originalIndex  to=newIndex  val=value } This indicates that an element in the list was moved from one starting index to another.
{type='add'  index=index  val=value } This indicates that elements in the list were inserted at the given index.
{type='remove'  index=index  val=value } This indicates that an element in the list was removed at the given index.

var a = {x={r=3}} var b = {x={r=4}} dif[a b] ; returns {['x','r']: {type='set' val=4}}

Note that iterating through this object will return changes in such a way that if they're applied one at a time to the old object, you'll get the new object (with respect to non-privileged members of course).
condName:: testExpression condition: condition : [ ]

is useful for making switch-statement-like constructs and more powerful templatized conditionals.

The testExpression is evaluted for each of the condition. Has the implicit variable v that holds the value of the condition being tested. The implicit variable can be renamed using ::, which renames the condition parameter to condName. If the testExpression returns true, the are executed and the is complete. If not, the next set of condition are tested. If there is an , the under it are executed if none of the condition yield a true result from the testExpression.

cond 50<v.height[] personA: logger.i["The first"] personB: logger.i["The second"] else: logger.i["Nobody"] ; you can use else to attach fallback testFunctions cond 50<v 34: doSomething[] 12: doSomethingElse[] else: cond v%10 > 5 4: doSomething[] else: doSomethingElse[] ; renaming the condition parameter cond x:: x.has[4] {1 2 4}: doSomething[]

See also: "Emulating switch statement fallthrough in Lima" under Concepts.

object value key count : [ ]

Lima's main loop construct (stands for "do for"). Iterates through each item object returned by object's IterList method. value, key, and count will be automatically declared as var variables. On each iteration, value contains the value of the next member in object, key contains the key for that value, and count contains a count of the members that have already been looped over.

Note that when using the default object iterator, the loop gets a reference-copy of the object (a list of references to the object's members - like a list-slice) that has references to the original object's members. This means that removing, adding, or rearranging any elements in the original object won't affect the progression or number of iterations of the loop.

count is optional, and key is optional if there is no count. df called with no parameters (object, value, key, or count) is an infinite loop.

Macro that jumps straight out of the loop, skipping any lines of code in the current loop's block. If referenced from a pointer, break will jump to the break location for where it was referenced from:

df: ; infinite loop var outerBreak = break~ var n=0 while true n+=1 : if n > 5: outerBreak ; when n is 6 it will jump to after the infinite loop

break has one member:

contin The continuation break will break to - ie the line right after the loop. Just like break, if referenced from a pointer, continue will jump to the continue location for where it was referenced from.

Macro that skips the rest of the loop and continues with the next iteration of the loop.

continue has one member:

contin The continuation continue will continue at - ie getting the next value, key, and count of the object before executing the statements again from the top.

: [ ]

Just like a rawthread, but its statements are run atomically, meaning that conceptually, once the thread starts running, nothing else is run concurrently. This allows programs to have much more deterministic robust behavior, avoiding an infinity of race conditions that can occur in true concurrent code. As long as two threads don't have conflicting code (eg writes to share variables), the compiler may optimize the code to run truly concurrently. Note that the main thread is *not* run atomically, and so if a thread may modify a variable while it is in an inconsistent state in the main thread, an atomic block should be used in the main thread.

This is more similar in concept to setTimeout in a language like Javascript than threads in a language like C or Java.

:  parameters unmake: [:  parameters unmake: ]

Object constructor. Implicitly creates a copy of the this, runs the statements in make for that new object, and also returns it implicitly.

var x = { var[a b] make this.a x: b = 5*x } x[1 2] ; returns a copy of x that looks like {a=1 b=10 fn! make}
The method, however, may explictly return, in which case it returns that alternate object instead (and the implicitly created object is discarded - or really [under the hood] is never created in the first place).
var x = { make a x: ret {a=a x=x} } x[1 2] ; returns {a=1 b=2}

make overrides the bracket operator for the object it is written, but any object created via the constructor does *not* have the constructor and retains whatever other bracket operator may be defined.

var x = { operator[ [ ] = fn val: ret val+1 make: ret {a=3 b=5} } var newX = x[1 2] ; returns {a=3 b=5} newX[99] ; returns 100

The statements under the unmake section describe the destructor/finalizer. This is code that run when one of the following happens:

  • the object goes out of scope and it has no strong-references pointed to it (note that closures may implicitly have strong-references to the object)
  • the object's destructor is explicitly overwritten with a different destructor or deleted, for example, if an object in a nilable variable is set to nil.

The unmake of the object returned from make is guaranteed to be run (unlike in languages like python) as long as the program isn't forcibly killed. Destructors are called top-down if possible, meaning that objects that no other (undestructed) object is pointing to will be destructed one by one until none are left. In the case of circular references, destructors are called in the reverse-order the objects were created. To control the order of destruction, a nilable object can explicitly be destructed by setting it to nil. An exception is thrown if the object is resurrected and any new strong-reference to the object is set to nil.

By default, if the environment compiled to supports garbage collection, the unmake block will be run some time after the object becomes inaccessible (and before the end of the program). If you want to change that behavior, use an assert to specify tighter constraints, for example:

var x = { make: this.connection = getConnection[] unmake: var t = unixtime.after: this.connection.close[] assert(meta[this].detachmentTime-t < 5) ; the destructor must be called within 5 seconds of the object becoming inaccessible } var newX = x[1 2] ; returns {a=3 b=5} newX[99] ; returns 100
Constraining the time the destructor is called can prevent some potential garbage collection optimization, but can guarantee more deterministic behavior.

[:  parameters ]

Overloads operators ==, !=, <, >, <=, and >= all at the same time. The should only have one parameter (the parameter to compare the object to). Should return true if the value is greater than the object, and false otherwise. It will infer everything else from this return value.

Note that this implicitly creates overloaded operators in the same scope as the object the is defined in, and Lima will complain if the creates a overloaded operator whos parameters conflict with an existing operator.

Makes a variable not accessible outside its .

Private members of compiled files are not externally available (when statically or dynamically linking to it), while public members are.

use[packagePath packagePath2 ...] A convenience function that allows dry module loading. Each packagePath is a string containing the path to a module package to load. Each passed module module path will automatically set a variable with the same file name as the value passed in (minus the extension). The following are equivalent:
use['x@2.1.0' '../y/someModule'] var[ x=load['x@2.1.0'] someModule=load['../y/someModule.lima'] ]
with variable1 variable2 ...: A with block creates a new isolated scope that only passes through the ability to access the specified variables. The return value of the statements are returned from the with call. Allows a programmer to easily section off blocks of code that are relatively independent, which can make it a lot easier to refactor later, especially if you want to turn those blocks into separate functions.
var x=1, y=2, z=3 var result = with x y: var c = 3 logger.i[x] ; works fine logger.i[z] ; doesn't work return x+y+c logger.i[c] ; doesn't work because the c defined inside the with block isn't in scope
Lima's with block is what Jai calls a "capture".
change[testStatement:oldValue changeStatments]: statements

On-change event statement. The statements are run when one of the given testStatements changes value. For each testStatement, oldValue (and the preceding colon) is optional, but if declared, the name given for oldValue can be used to access the previous value of the testStatement.

Returns an object with the following pseudo-methods

  • stop - stops the listener (the event-handler won't be run for subsequent changes to the testStatement).

The event-handler is triggered synchronously on change of whatever variable caused the testStatement to change. After any triggered event-handlers run, the code that triggered the change is continued from. Any event-handler triggered simultaneously in a change event are executed in parallel atomic blocks.

mut x = 5 change[x]: logger.i['x changed to 'x] x = 6 ; the event handler prints to the console before the following statement logger.i['hi'] ; keep track of the change in value mut delta = 0 change[x<5:lastValue]: delta = lastValue - x ; maintain a = 2*b+c^2 mut a = 2*b+c^2 change[c b]: a = 2*b+c^2

Note that you have full control over the number of times the statements are executed because one event is handled fully before the next event can be handled. Even though an event is fully handled before the next one is handled, all events are guaranteed to be caught, even if one happens while another one is being handled.

This is implemented using the history meta property.

Standard Stack Attributes

logger[level message data] Holds a function that handles logs. Has the same form as logger[]. By default, prints all levels except 'warn', 'error', and 'severe' to stdout. The 'warn', 'error', and 'severe' levels are printed to stderr by default.

Lima's philosophy is pretty similar to "the-right-thing" approach, with reversed importance between consistency and simplicity, and some additional caveats about completeness. This is very much not a "worse-is-better" philosophy. A general purpose programming language is basically the base layer of all software, and it should be done right, not quick and dirty. In comparison to the "get-stuff-done" approach, Lima's philosophy is similar except that performance isn't really a factor, and Lima's philosophy is more specific about what it means by completeness.

Lima's guiding principles:

  1. In some languages, you can access items from the end of an array like this: someList[-4]. This would access the 4th to last element of the array. Lima doesn't have that syntax (since any value is already a valid key to use in an object), but you can do the same thing in one of two ways:
    {1 2 3 4 5}.sort[-v][3] ; sort the list in revrse, then select the the 4th element (the 4th to last element of the original list - 2) x = {1 2 3 4 5} ; .. x[x.len-4] ; another way to access the 4th to last element
    Because lima should optimize this, using sort should be just as good as using the length.
  2. x.group[v].keys
  3. To emulate a do-while, you can have an infinite loop that has a break condition at the end of it:
    while 1: ; the 'do' of C's do-while logger.i "I'm finally in the loop!"@ ; .... if someCondition: break ; the 'while' of the do while
  4. var x = {1 2 3} x.sort[-k] ; sort by the negative of the index (key) ; returns {3 2 1} var y = {1 5 2 5 93 0 -4} y.sort[-v] ; sorts on the negative of the value ; returns {93 5 5 2 1 0 -4} y.sort[[ !(v<v2) ]] ; the complement of any expression in sort[[ ]] is a reverse sort ; also returns {93 5 5 2 1 0 -4}
  5. Because lima will optimize away most unneccessary processing, finding a minimum can use the first value returned from a sort. It might seem nice to have a minimum function, but what happens when you need to find the 4th lowest value in a list? Or if you need all 4 lowest values? Using sort gives a good generalized way of finding this type of thing:
    var x = {5 2 87 9 6 8 4} x.sort[v][0] ; sort by value and get the first element - the minimum x.sort[v][3] ; find the fourth lowest value x.sort[v][0..3]] ; get the 4 lowest values x.sort[-v][0] ; find the maximum
  6. Lima doesn't have a `static` keyword like C++ does, but it can still do the same thing. You just need to use a reference:
    var class = { ref myStaticProperty ~> 0 make: myStaticProperty += 1 } var newInstance1 = class[] logger.i[class.myStaticProperty] ; prints 1 logger.i[newInstance1.myStaticProperty] ; prints 1 var newInstance2 = class[] logger.i[class.myStaticProperty] ; prints 2 logger.i[newInstance1.myStaticProperty] ; prints 2 logger.i[newInstance2.myStaticProperty] ; prints 2 class.myStaticProperty = 3 logger.i[class.myStaticProperty] ; prints 3 logger.i[newInstance1.myStaticProperty] ; prints 3 logger.i[newInstance2.myStaticProperty] ; prints 3 newInstance1.myStaticProperty = 4 logger.i[class.myStaticProperty] ; prints 4 logger.i[newInstance1.myStaticProperty] ; prints 4 logger.i[newInstance2.myStaticProperty] ; prints 4
    In the above case, its pointing to an immediate value, so changing it isn't a problem. But if you want to create a static variable initialized to a copy of a variable rather than referencing that variable, like static members in C++, just create a copy first:
    var x = 5 var class = { static: var internalX = x ref myStaticProperty ~> internalX make input: myStaticProperty += 11 } var newInstance1 = class[] logger.i[class.myStaticProperty] ; prints 16 logger.i[newInstance1.myStaticProperty] ; prints 16 logger.i[x] ; prints 5
    Note that there's no equivalent of a static function variable. This differs from many languages that allow static function variables. To do this in Lima, you just need to use a variable in a higher scope.
  7. If you select a slice of a list or object, the keys are preserved. In such cases, you might want to rekey the array, so that all its members are elements.
    "hello"[[k>2]] ; returns the object {3:'l' 4:'o'} "hello"[[k>2]].sort[k] ; returns the object {0:'l' 2:'o'}, or equivalently "lo"
  8. You can emulate classical string join (implode in php) using the pseudo-method ins and the join method:
    var L2 = {1 2 3 4 5}.ins[[', ']] ; this returns the list {1 ', ' 2 ', ' 3 ', ' 4 ', ' 5} logger.i[L2.join[a.cat[b]]] ; this will print the list with commas
  9. The trick is to make a reference to the object itself
    x = { mythis ~> this ; mythis points to this a=5 b=23 c=89 obj= { a = 'hi' b = a ; b gets the value 'hi' func = fn [ var b=34 ; errors, becuase b is already declared in the current object var c="nope"; errors, because c is already declared in a containing object logger.i a ; errors, because a is an ambiguous name logger.i this.a ; prints "hi" logger.i this.b ; prints "hi" again logger.i mythis.a ; prints 5 logger.i c ; prints 89 logger.i mythis.c ; also prints 89 ] } }
  10. x = { a = 5 t = fn [ ret 'x' ] } y = { private mix x:! t ; using all x's members except t t = fn ; override t [ ret oldt[].cat['y'] ] private mix x[t : oldt] ; using x's t member aliased as private member oldt } y.t ; returns 'xy' y.oldt[] ; returns 'x'
  11. Lima doesn't quite have the same kind of generics that a language like Java has, because values in Lima don't have a "type" (instead a given type-set can either include a value or not). However, you can still do most of the things you would want generics for using variables containing types:
    var Statistics = fn type T: ret { list[T] percentiles ; 0, 20, 40, 60, 80, 100 T mean T median } var x = Statistics[int] ; returns a statistics object where the type T is 'int'
    In Lima, lists of a subclass can be passed anywhere a list of a superclass is required - ie covariencea> is assumed. This isn't true in, for example, java. Here's an example in Lima:
    var Sublist = { mix[list] first = fn: ret alist[0] } var see = fn list[int] alist: logger.i[list] Sublist[int] x = {1 2 3} see[x] // is callable even tho the parameter is required to be a 'list[int]'
  12. Generators and custom iterators aren't neccessary in lima, because optimizations like lazy list building (infinite or finite lists) and futures/promises, take the burden off the programmer, and puts it on the compiler/interpreter. Things done with coroutines should be done using normal list semantics (which makes code more consistent and allows the list results from these to be operated on by any function that operates on a list). Generators, custom iterators, and corountines can all be implemented either using function immediates, list semantics, or threads. For example, a custom iterator:

    var threeTimes = fn f: df 13: f[] threeTimes[fn: logger.i 'cry'@ ]
    or a generator:
    createInfiniteList = fn: var theList = {} var next = 1; while true: infiniteList.=ins[next] next++ ret theList infiniteList = infiniteList[] logger.i[infiniteList[3]] ; prints 4

    Lima doesn't need semantics like yield to impliment coroutines. In ECMAScript 4:

    function fringe (tree) { if (tree is like {left:*, right:*}) { for (let leaf.in fringe(tree.left)) yield leaf for (let leaf.in fringe(tree.right)) yield leaf } else { yield tree } } /­/; tree var tree = { left: { left: 37, right: 42 }, right: "foo" } for ( let x.in fringe(tree) ) { /­/; Iterate over the tree trace(x); /­/; 37, 42, "foo" }

    The same can be done much more simply in lima like this:

    fringe = fn tree: ; returns a (possibly lazily constructed) list if like[tree {var[left right]}]: ret fringe[tree.left].cat[fringe[tree.right]] else: ret {tree} var tree = {left = {left=37 right=42} right='foo'} df fringe[tree] : x : logger.i[x]
  13. chooseset[int string fn!] ; maybe this is wrong?
  14. Tho there would probably be no reason to ever create a linked list in Lima, it's informative to show how one of the most common interview questions is done:
    // head node of a linked list node = { ref[node!]? next } reverse = fn mut node! head: mut ref? previous ~> nil while head != nil: {head.next previous head} ~> {previous head head.next} head = previous main: node! head = {"A" next~>{"B" next~>{"C" next~>{"D"}}}} reverse[head]
  15. aUnion = { chooseset[real int string] value ; using a set of types as a tuple type acc f ; real accessor [ ret ref[value] ] acc i [ ret this.f ; int accessor ] acc s [ ret this.f ; string accessor ] }
  16. You can use arb to implement don't cares in an if statment:
    if: a==5 $ arb[{true false}] ; do something
    This means that it will always ; do something if a equals 5, but if a does not equal 5, then the compiler can choose whether or not to execute that code (if it somehow simplifies the logic of the program)
  17. In SQL:
    select productid, sum(unitprice * quantity) as total from o in db.orders inner join rd in db.orderdetails on o.orderid == rd.orderid where o.zipcode == 94503 group by rd.productid

    and in Lima:
    db.orders.map[ {mix[ v db.orderdetails[[o: o.orderid == v.orderid]][0] ]} ] ; join [[ v.zipcode == 94503 ]] ; where .map[ {productId=v.productId price=v.unitprice*v.quantity][0].rd ; .group[v.productId].map[{ v[0].productId v.join[a.price+b.price] }]
  18. In XQuery:
    for $b in $bs/book return <result> {$b/title} {$b/author} </result>
    And in Lima:
    bs.book[[ v[{'title' 'author'}.has[k]] ]] ; one way bs.book[[ {title=v.title author=v.author} ]] ; another way
  19. mut ref.fn theFunc anonFuncCreator = fn list[string string] characters: ; takes in a mapping from character to character theFunc ~> fn string a: ; create anonymous function var charactersCopy = characters ref.(list[string string]) map ~> charactersCopy if {a} < map.keys: ; if map has the key a ret map[a] else: ret a ; returns input character if there is no mapping
  20. In hardware design, a wire is what connects an input of a device to the output of another device. In Lima, one can describe the same functionality as a wire using functions:
    main = fn: int[ mut[a=2] b=5 c=13] fn! aWire = fn[ ret c+b*a ] ; acts like a wire logger.i aWire[] ; will print '23' (13+5*2) a+=4 logger.i aWire[] ; will print '43' (13+5*6)
  21. There are two ways to do event-driven-like programming:
    • list iteration and slicing
    • variable observation

    In traditional programming languages, programming with events is done by setting up callback handlers that are called when an event happens, for example in javascript:

    emitter.on('someEvent', function(eventData) { doSomethingWith(eventData) }) ... emtter.emit('someEvent', {your:'data'})

    But in Lima, we can do this kind of thing with standard lists and the tslice function:

    mut events = [] ; this event isn't printed because it happens before tslice was called events.=cat['1'] var stop = future var relevantEvents = events.tslice[stop] events.=cat['2'] df relevantEvents event: ; this loop prints '23' logger.i[event] events.=cat['3'] stop.done ; this event isn't printed because it happens after the 'stop' future is resolved events.=cat['4']

    Another way to do event handling is by doing variable observation using the 'change' function:

    mut events = [] ; this event isn't printed because it happens before the change handler is set up events.=cat['1'] var c = change[events:oldEvents]: var d = dif[oldEvents events] df d.elements.add event: ; assume all changes are appends logger.i[event] ; this loop prints '2' events.=cat['2'] c.stop ; this event isn't printed because it happens after the 'change' handler is stopped events.=cat['3']

  22. In Lima, circular dependencies can not only happen for module dependency loading, but can also happen because of dependencies between variables in the code. For example:
    mut x = 5 mut a = {1 2 3} var slice = a.tslice[process.end] df slice v: if v == 4: x = 10 if x > 6: a.=cat[4]
    The above code can never finish because the if statement doesn't know the value of x. The loop is also waiting to see if a will have a 4 appended to it, which it might if x becomes more than 6 because of the loop. This behavior should throw an exception if detected.
  23. change[a:oldValue]: if oldValue == 0 && a==1: ; reacts to a positive edge: 0 to 1 ; do something oldValue == 1 && a==0: ; reacts to a negative edge: 1 to 0 ; do something
  24. var o = {1 3 9 12 15} var o2 = {var[x=90]} o.keys >= {2} ; true (has the value 9 at index 2) o.keys >= {12} ; false o2.keys >= 'x' ; true since o2 has a defined member x
  25. Testing set comparisons between two lists can be done in multiple ways. For consistency, only one of those ways is preferred and the compiler will correct code that uses the un-preferred comparisons.
    1. How to test if an item is contained in an object/list

      • Preferred: {theItem} < theList asks "Is the object consisting only of theItem a subset of theList?"
        var o = {1 3 9 12 15} var o2 = {'H'} {3} <= o ; true {2} <= o ; false {"H"} <= o2 ; true {"H"} < o2 ; false because {"H"} is not a proper subset of itself
      • theList[[{v}==theItem]].join[a$b] asks "Does any member in theList equal theItem".
    2. How to test if a list is an improper subset of another

      • Preferred: list1 <= list2 asks "Is list1 a subset of list2 or equals list2?".
      • list1[[ list2[[v2:v == v2]].join[a$b] ]].join[a&b] asks "Does every member in list1 equal some item in list2?"
    3. How to test if a list is a proper subset of another

      • Preferred: list1 < list2 asks "Is list1 a proper subset of list2".
      • list1[[ list2[[v2: v==v2]].join[a$b] ]].join[a&b] & list2[[list1[[v1: v!=v1]].join[a$b] ]].join[a&b] == no asks "Does every member in list1 equal some item in list2? And is there some item in list2 that doesn't equal any item in list1?"
      • list1[[ list2[[v2: v==v2]].join[a$b] ]].join[a&b] & list1!<>list2 asks "Does every member in list1 equal some item in list2? And does list1 not contain all the members that list2 contains?"
    • Preferred:

      var conditions = fn k: (200285).has[k] var charactersToCut = aList[[conditions[k]]] aList.rm[[conditions[k]]]

      After this code is run, aList's elements with keys larger than 285 are shifted "left" so that an originally having the key 286 will now have the key 200, etc. This method works on members that are not elements, but no shifting happens.

    • var charactersToCut = aList[[(200285).has[k]]] aList = aList[ !({k} < charactersToCut.keys) ].sort[k] ; get the list without the cut characters and rekey them (via sort)
    • var charactersToCut = aList[[(200285).has[k]]] df charactersToCut v k: aList[k] = nil ; delete all the members that were cut
  26. Files are always opened as binary in Lima. Text-mode, in a language like C, encodes newlines in a platform specific on certain platforms. In reality, this is just another encoding, and Lima treats it that way. If you need to handle operating-system specific newlines, you should open a file with the appropriate decoder that handles those OS-specific newlines (e.g. dosText).
  27. The first means "Does every member in list equal some member of list2?", while the second means "Does any member in list2 equal every member of list2?". Example,
    {1 2 3}[[ {4 5 6}[[v2: v == v2]][$] ]][&] ; is the same as: {4 5 6}[[v2: 1 == v2]][$] & {4 5 6}[[v2: 2 == v2]][$] & {4 5 6}[[v2: 3 == v2]][$] ; which is the same as: (1==4 $ 1==5 $ 1==6) & (2==4 $ 2==5 $ 2==6) & (3==4 $ 3==5 $ 3==6) ;while {4 5 6}[[ {1 2 3}[[v2: v == v2]][&] ]][$] ; is the same as: {1 2 3}[[v2: 1 == v2]][&] $ {1 2 3}[[v2: 2 == v2]][&] $ {1 2 3}[[v2: 3 == v2]][&] ; which is the same as: (4==1 & 4==2 & 4==3) $ (5==1 & 5==2 & 5==3) $ (6==1 & 6==2 & 6==3)
  28. list[[v==value]].len counts the number of members that equal value
  29. list[[v==value]].keys[0] finds the index of the first member of the list that equals value
  30. mut x = 5.23456 var floor = x//1 ; exactly like floor var ceiling = x//1+1 ; exactly like ceiling
  31. The Haskal list comprehension syntax:
    s = [ 2*x | x <- [0100], x^2 > 3 ]
    and the equivalent python list comprehension syntax:
    s = [2*x for x.in range(100) if x**2 > 3]
    can be done in Lima by doing:
    s = (0..100)[0 100][v^2 > 3][[v*2]]
  32. The filter function in python and other languages creates a new list of items from the items in a list that cause some function to return true. For example, "filter(list, GT4)" might return a list of elements that are greater than 4. In lima this can be done like this: list[[v>4]]
  33. To select a list of second-dimension items from a multi-dimensional list, you'd use the map method. E.g.:
    var rowFirstMatrix = { {1 2 3} {4 5 6} {7 8 9} } rowFirstMatrix[2] ; selects the second row: {4 5 6} rowFirstMatrix.map[v[2]] ; selects the second column: {2 5 8}
  34. If you want an enum in Lima, you can create type sets. For example:
    type color = type.set['blue' 'green' 'purple'] fn[[color:]] colors = fn int x: if x == ?0:: 1: ret 'blue' 2: ret 'green' 3: ret 'purpel' ; Compiler should complain that the misspelling of 'purple' doesn't match the function's return type. var c = colors[1];
    If you don't need/want to explicitly enumerate all possible values, you can return strings and in most cases your IDE or compiler should still be able to tell you about code paths that can never execute.
    var colors = fn int x: if x == ?0:: 1: ret 'blue' 2: ret 'green' 3: ret 'purple' var color = colors[1]; if color == 'green': logger.i['GREEN!'] color == 'purpel': logger.i['PURPLE!'] ; IDE/compiler/interpreter should tell you this code path can never happen because the function will never return that misspelling 'purpel'
  35. When a variable has a particular spot in your program that it is set, it is best to let it be set there, rather than setting it in both there and at the declaration. The reason for this is that you can find problems earlier if you don't build redundancies into your program. A redundancy can make your program work the first iteration of a process, but then it will fail the second time (or later) - which is a much more mysterious problem to debug.
  36. Lima doesn't have traditional inheritance. Lima instead takes the approach of languages like lua, lisp, and Ruby in providing "mixins" or "traits" rather than parent-child inheritance. Lima's form of inheritance is what's formally referred to as 'traits' (tho unlike tranditional traits, objects can inherit state) and are similar to mixins. Members from one object can be mixed into another, but there is no sub-class/super-class relationship, and no "is-a".

    Any conflicting members must be explicitly disambiguated (by renaming one or both members, or excluding one or both members). A consequence of this is that there is no automatic solution to the diamond problem, and solving any diamond problem is left to the programmer (tho the compiler or interpreter will not fail silently but will force a solution to be made). Lima opts to not have inheritance for the same reason Go opts not to (http://golang.org/doc/go_faq.html#inheritance), inheritance is too complicated and can cause insidious bugs. However, Lima's trait-like system gives the programmer the flexibility of multiple inheritance, with the safety of single.

  37. Lima doesn't offer an atexit function. This is because atexit functions are prone to cause scattered code. Instead, if you want to do something like this, create a function that does exit code. Whenever you need to For example:
    fn exitFunc [ 5+4 doSomeFunction[ ] obj.save[ ] ; etc exit ]
    You can enforce that this function is used in place of exit by using the exitFn attribute. Also, object desctructors could cover the use cases you would use atexit for. Or possibly you could use a try-finally block for this as well.
  38. Lima was built with the intention of being fully automatically opimized, and therefore doesn't have any specification of concepts like tail-calls that affect the speed of a program, but not the output. In short, any reasonable Lima implementation should implement proper tail calls, but it isn't part of the language.
  39. Lima doesn't do short circuit evaluation by default (tho it may optimize certain boolean operations such that they do short-circuit evaluation under the hood). So how do you do it when you want it?

    ;In java, you might want to do things like this every once in a while: if( y != 0 && 5/y > 1 || x.equals("yes") && testFunctionWithSideEffects() || z != null && z.isTrue || w != null && w.method() ) { doSomething(); } ; In lima: if if[y!=0: 5/y>1 else: false] ; use if's return value $ if[x=='yes': testFunctionWithSideEffects[] else: false] $ z?.isTrue?[] == true ; use the safe-traversal operator $ z?.method?[] == true ; have to check if its true because it could be nil

  40. String interpolation is really just another way to concatenate variables with strings On the surface, string interpolation seems like a nice feature that makes strings more concise. The downsides, however, far outweigh that small benefit. I'm lumping using special characters (like "\n" and "\t" etc) under the umbrella of string interpolation.
    • Increase cognitive load. Really interpolation is its own little mini language that does the exact same thing as the main language. Just using concatenation doesn't require extra knowlege. Writing cat["x is "x", f(x) is "f[x]] in perl is "x is $x, f(x) is @{[&f(x)]}" - yuck.

      And it is not only one-time knowlege, it requires constant vigilence. If you want to type literal strings, you need to make sure you escape anything used as special characters or interpreted in any way other than literally.

    • Its not that much shorter than lima:
      ; ruby "the meaning of life is #{11+31}!" "#{a} and #{b} and #{c} and #{d}" ; lima "the meaning of life is ",(11+31),"!" cat["the meaning of life is "11+31"!"] cat[a" and "b" and "c" and "d] ; oops this is actually shorter in lima! ; perl - perl's a little shorter tho "$a and $b and $c and $d"
    • Complicates escaping in pasted text and generated code. Lima's strings can contain any pasted text as long as quotes are escaped. Contrast that with ruby where not only do you have to escape any instance of "${" and "\" as well.
    • Many implementations of string interpolation are templates that are then filled in by values. An example of this is printr in C. The problem with this implementation is it visually separates the variables from the string, making it harder to read. Some may argue this separates presentation from data, but thats not correct. It only separates related code, which isn't good.
      ; for example templateA = fn int[a b]: ret ''.cat[a" and "b] ; the following is much harder to read templateB = fn int[a b]: ret "%s and %s".interpolate[a b] ; hypothetical interpolation
    Embedding code in strings solves a non-existant problem. If you want to embed expressions inside strings, ask yourself, why you aren't using variables?
  41. Lima's change and dif functions allows you to easily observe objects in an event-driven way
    var x = 3 change[x]: logger.i['x changed to:'x] x = 4 ; 'x changed to : 4' is printed var y = {1 2 3} change[y:oldy]: var d = dif[oldy y] if d.set != nil: logger.i['y was set to a different object'] df d.elements v: if v.type == 'add': logger.i['y got a new element: 'v.val] v.type === 'move': logger.i['y got shuffled around'] v.type === 'remove': logger.i['y lost an element to the war :(']
  42. The attributes and allow a programmer to specify how external output may be grabbed before it's used. But for statements without this attributes, the optimizer can choose whether to grab a file lazily or not (it might not if it thinks starting loading some output will lower latency later) and there is no attribute to specify explicitly lazy data grabbing. So while often things are lazily loaded by default, if you want to ensure something is lazily loaded, you should include optimizers (or write your own) that make that determination.

  43. A useful use of macro functionality is to modify normal syntax to mean something not-so-normal. For example, a macro could be defined such that this:

    atomicAssignment [ a = getFromServer['a'] b = getFromServer['b'] c = getFromServer['c'] d = getFromServer['d'] e = getFromServer['e'] ]
    is transformed into:
    var temp_a = getFromServer['a'] var temp_b = getFromServer['b'] var temp_c = getFromServer['c'] var temp_d = getFromServer['d'] var temp_e = getFromServer['e'] atomic statements1: a = temp_a statements2: b = temp_b statements3: c = temp_c statements4: d = temp_d statements5: e = temp_e ]
    This would have the benefit of being much easier to write, while also allowing all those variables to be swiched at exactly the same time. This could be useful, for example, for programming a caching mechanism that refreshes periodically.

  44. The placement of the * and & symbols in C are not very intuitive, and force the reader to mentally parse a group of very similar looking syntax into very different semantics (values vs addresses). Lima's pointer syntax aims to reduce this cognitive load. As an (imperfect) quantatitive measure, we can count the number of "weird" symbols in this set of statements:
    ; C Lima int a=5 mut int a = 5 int *p, *p2 mut ref.int[p p2] int** p3, **p4 mut ref.ref.int[p3 p4] p = &a p ~> a p2 = p p2 ~> p p3 = &p p3 ~> p~ *p = a p = a *p = *p2 p = p2 *p = **p3 p = p3 p = *p3 p ~> p3 a = **p3 a = p3 p4 = p3 p4~ = p3~ *p4 = *p3 p4 ~> p3~~ **p4 = **p3 p4 = p3
    C has a count of 22 "weird" symbols (consisting of the structures: '*' and '&'), whereas Lima has a count of 13 weird symbols (consisting of the structures: 'ref', '~', and '~>'). This significantly lower number indicates that the reader doesn't have to do as much mental work to understand the statements. Lima's reference syntax is easier than C's when pointers are being pointed to normal values or are being used as the values they reference, but is harder when pointers are being pointed at other pointers, especially for higher level pointers (a pointer to a pointer to a pointer... etc). The argument here is that pointers are more often pointed at normal values and being used as those normal values, than being pointed at eachother. And higher level pointers are very rare.
  45. var sparseMatrix = { private var internal = {} access get else index: ret interal[index] | 0 set else index value: internal[index] = value }
  46. Warp the objects in your scene if you want to change the aspect ratio of the whole scene.
  47. In languages like C, a switch statement allows what's known as "fallthrough" where the statements under a 'case' continue onto the statements in the next 'case' unless you 'break' out of that case. Here's an example in C:

    switch( someValue ) { case 1: printf("One"); break; //.... case 20: printf("Twenty"); case 21: case 22: case 23: printf("Twenty-three"); }
    If someValue is 1, "One" will be printed, since it breaks out of that case. If someValue is 23, "Twenty-three" will be printed, because there are no cases after it. If someValue is 21 or 22, "Twenty-three" will still be printed, because they simply fall through to case 23's statments. If someValue is 20, "TwentyTwenty-three" will be printed, because case 20 has its own statement but still continues on to the statements in the next case (cases 21, 22, and 23). That last sequence of events for case 20 is generally considered pretty evil, because it's hard to read and it's easy to make mistakes that way.

    Lima doesn't have fallthrough, but there are a couple ways to write the same behavior in more readible ways. This Lima code does the same thing as the C code above, use the statement:

    cond fn[x: ret someValue==x] 1: logger.i['One'] ;.... else: cond fn[x: ret x.has[someValue]] {20 21 22 23}: if someValue == 20: logger.i['Twenty'] ; only print 'Twenty' if the value is 20 logger.i['Twenty-three'] ; print 'Twenty-three' for all four values

  48. Here are four very similar examples that show how using threads and the attributes and affect program flow.
    ; the order of grabbing those files is potentially asynchronous (order doesn't matter), ; so they can all potentially be grabbed at once ; however output is assumed to be sequential (order matters) so this will always output ; the length of A.txt first, B.pdf second, and C.html third ; example output if ; A.txt is 100kb and takes 1 second to download, ; B.pdf is 10 bytes and takes 1 ms to download, ; C.html is 1000 bytes and 10 milliseconds to download, and ; we're ignoring the trivial amount of time the rest of the code takes: ;[ A.txt: 100000 - at 1 seconds B.pdf: 10 - at 1 seconds C.html: 1000 - at 1 seconds ;] getLength = fn host file: tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len main: var startTime = time.before: mut lengths = {} df {"A.txt" "B.pdf" "C.html"} file: lengths[file] = getLength['www.w3.org' file] df lengths length file: var endTime = time.after: logger.i[file': 'length' - at: 'endTime-startTime' seconds'@]



    ; input is asynchronous here again, the default ; since output is also asynchronous between threads (obviously), its sequential per thread, ; the lengths will be printed as those files come in ; As far as speed, this program won't complete any faster than the previous example, but the first file printed may happen faster ; since A.txt might not be the first file to download, a file that took less time to download will be printed first ; example output with same conditions: ;[ B.pdf: 10 - at 0.001 seconds C.html: 1000 - at 0.010 seconds A.txt: 100000 - at 1 seconds ;] getLength = fn host file: tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len main: mut lengths = {} df {"A.txt" "B.pdf" "C.html"} file: lengths[file] = getLength['www.w3.org' file] df lengths length file: var endTime = time.after: thread: logger.i[file': 'length' - at: 'endTime-startTime' seconds'@]



    ; input now waits on the last input to complete, meaning that those files are pulled in synchronous order ; even tho output is running in separate threads, the output is forced to be sequential (A.txt first, B.pdf second, and C.html third) ; This way will definitely be slower since the files can't download in parallel ; example output with same conditions: ;[ A.txt: 100000 - at 1.011 seconds B.pdf: 10 - at 1.011 seconds C.html: 1000 - at 1.011 seconds ;] getLength = fn host file: tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len main: mut lengths = {} mut nextFuture = future[nil] ; an immediately resolved future df {"A.txt" "B.pdf" "C.html"} file: nextFuture.wait: nextFuture = future lengths[file] = getLength['www.w3.org' file] df lengths length file: thread: logger.i[file': 'length@]



    ; the input is now marked 'ready' and so is ready whenever the system wants to get it - at best (and in this case) ; that means compile-time ; the output is still asynchronous, but it wouldn't matter if it wasn't - the running time and output would be the same ; This way is the fastest since you don't have to do as much work at runtime (in this case its pretty much instant) ; The output may still be sequential (as shown), but that isn't required ; example output with same conditions: ;[ A.txt: 100000 - at 0 seconds B.pdf: 10 - at 0 seconds C.html: 1000 - at 0 seconds ;] getLength = fn host file: tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len mut lengths = {} df {"A.txt" "B.pdf" "C.html"} file: ready lengths[file] = getLength['www.w3.org' file] df lengths length file: thread: logger.i[file': 'length@]
  49. In node.js, npm's package.json has devDependencies and optionalDependencies. In lime (lima's package manger), devDependencies aren't needed because those can simply be put in the "ignore" list so as to not be published at all. They can be accessed via the package's development repository if wanted.

    Optional dependencies aren't needed because Lima installs dependencies on run of a program that wants to load it, rather than having a separate install command needed. If a dependency fails to be loaded, the lima program trying to load it will see an exception and can deal with it appropriately if it wants to.

  50. Program exit should always be under the full control of the top-level module. Dependencies of a module shouldn't be allowed to hijack the program by killing it without going through proper error handling channels first. Therefore, the main program is allowed to change the exitFn attribute's bound value so other code won't directly exit the program. This can be used to capture the exit call and handle it appropriately. For example:

    var exitHandler = fn code: logger.i['Exit called within ShraffersExternalModule with code: 'code] exitFn.as[exitHandler]: ShraffersExternalModule.dangerousCall[]

  51. So since Lima uses lists for a range of things (strings, arrays, streams, generatorss, etc), one question would be whether back-pressure can be implemented in Lima. The answer is yes. All you have to do is have some external condition for halting the output of a list until that external condition changes. Here's an example:

    ; example forthcoming - Count the number of bytes that have been sent over the network for a file/stream var input = tcp.connect[adr[128.123.43.0] 'utf8'] var output = tcp.connect[adr[128.123.43.1] 'utf8'] mut paused=false middleMan = {} df connection.in.split[''@] chunk: if chunk === 'backpressure': paused = true chunk === 'readyForMore': paused = false else: middleMan.=cat[chunk] df middleMan chunk: if paused: var f = future change[paused]: f.return[] f.wait: output.out.=cat[chunk]

  52. Lima modules declared in the normal way are copies - modifying those copies won't affects anyone else using that module. For example, let's say you have the module:

    ; xModule var x = 5 var getX = fn: ret x

    If you use it like this in module A:

    ; module A use['xModule'] xModule.x = 40 xModule.getX[] ; outputs 40

    And then if module B uses xModule, it won't see those changes:

    ; module B use['xModule'] xModule.getX[] ; outputs 5, not 40

    This is different from things like node.js where, because variables are all references, changes would be seen. Even if module A up there set a reference to xModule, it couldn't modify the contents module B would see:

    ; module A private var xModule ~> load['xModule'] xModule.x = 40 xModule.getX[] ; outputs 40
    ; module B private var xModule ~> load['xModule'] xModule.getX[] ; outputs 5, not 40

    This is because load returns a copy of the module, not a reference to it. This is intentional - you don't want your dependencies unexpectedly interfering with eachother. If you want to see changes across modules, you can create a reference within xModule:

    var x ~> 5 var getX = fn: ret x

    This way, each copy of the module sees a reference to the same value. While changing where the reference points to won't be seen by other modules, overwriting the value the reference points to will be seen. The safest way of doing this is to allow only one module (probably your entrypoint module) to modify the dependency module (setting some global state). This can be done by doing something like this:

    ; xModule private var x ~> nil var getX = fn: if x == nil: throw "X not yet set" else: ret x var setX = fn val: if x == nil: x = val else: throw "X has already been set"

    This way, you can only call setX by one module (whoever calls it first) and other attempts to mess with global state will be met with an exception.

  53. Languages like Java disallow certain types of equivalencies with generics. For example,

    List<Number> a = []; List<Integer> b = a; // fails because a Number might not always be an Integer

    In Lima, this isn't usually a problem. An exception would be thrown only if an incorrect value actually gets set on the list:

    list[real] w = {1 2 3} list[int] x = w ; this is totally fine list[real] y = {1.2 3.4} list[int] z = y ; this throws an exception because y does not only contain ints

    However, this can lead to some confusing cases, like if you want to write to a list of supertypes when the object passed in is a list of subtypes:

    var add4point4 = fn mut list[real] x: x.=ins[4.4] list[int] w = {1 2 3} add4point4[w] ; throws an exception because 4.4 can't be added to w which is a list of ints

    If you would like to add more type information about a variable that could be used to catch errors like this at compile time, you could use the writeonly type to indicate contravariance.

  54. Tho some of these things would be better to be done in more than one line, this demonstrates how Lima uses some basic constructs to describable an infinity of possibilities. Going through the equivalents for PHP's associative array functions:

    ; array_change_key_case(input, CASE_UPPER) input.map[k.upper v] ; array_chunk(input, size) input.group[k/­/size] ; keys preserve, so if you don't want that then do input.group[k/­/size].map[v.sort[k2:k2]] ; array_combine(keys, values) keys[[ values[c] v]] ; values must be a list of elements ; array_count_values(input) input.group[v].map[v[0] v.len] ; array_diff_assoc(array1, array2, ....) array1[[ !({k:v} <<= array2 $ {k:v} <<= array3 $ ....) ]] ; array_diff_key(array1, array2, array3) array1[[ ! ((array2.keys & array3.keys).has[k]) ]] ; array_diff_uassoc(array1, array2, keyCompareFunc) array1[[ !(array2{[v2 k2: keyCompareFunc[k v k2 v2]]}.join[a$b]) ]] ; array_diff_ukey(array1, array2, keyCompareFunc) array1[[ !(array2{[v2 k2: keyCompareFunc[k k2]]}.join[a$b]) ]] ; array_diff(array1, array2) array1[[ !array2.has[v] ]] ; array_fill_keys(keys, value) keys[[k: value k]] ; array_fill(5, 10, value) (514){[v:value]} ; array_filter(input, callback) input[[ callback[v] ]] ; array_flip(trans) trans[[v k]] ; array_intersect_assoc(array1, array2, ....) array1[[{k:v} << array2.cat[....]]] ; array_intersect_key(array1, array2, ....) array1[[array2.cat[....].keys.has[k]]] ; array_intersect_uassoc(array1, array2, ...., compareFunc) array1[[ array2.cat[....].map[] ]] ; array_intersect_ukey($array1, $array2, ...., $compareFunc) array1[[ {k:v} << array2 $ {k:v} << array3 $ .... ]] ; array_intersect($array1, $array2, $array3, ....) array1[[ (array2 & array3 & .....).has[v] ]] ; array_key_exists($key, $array) array[key] != nil ; or array.keys.has[key] ; array_keys($array, $value, true) array[[v==value]].keys ; array_map($callback, $array) array[[ callback[v] ]] ; array_merge_recursive($array1, $array2, ....) ; There is no simple equivalent to this difficult-to-understand php function - and there shouldn't be. ; array_merge($array1, $array2, ....) ; Like array_merge_recursive, this method doens't make any damn sense, but here it is: fn[x arrs...: mut result = x df arrs arr: df arr v k: if IsValidIndex[k]: result.=ins[v] else: if result[k] == nil: result[k] = v else: result[k] = {k: {result[k] v} IsValidIndex = fn x: ret x.in 0..00 ][array1 array2 ....] ; array_multisort($array1, $array2) // heres another poorly designed php function array1.sort[] array2.sort[] ; why do you need to sort them in one function? Cause you're function names are so verbose? ; array_pad($input, $padSize, $padValue) df 0..padSize v: if input[v] == nil: input[v]=padValue ; that wasn't so hard was it? ; array_pop($array) var result = array[array.len-1] array.rm[array.len-1] result ; array_product($array) array.join[a*b] ; array_push($array, $val1, $val2, ....) array.=cat[ {val1 val2 ....}] ;array_rand($input) input[rand] ; array_rand($input, $num) 0..num[[ input[rand] ]] ; array_reduce($array, $function, $initial) array.join[initial function[a b]] ; array_replace_recursive($array, $array1, ....) fn[x arrs...: mut result = x ; create copy df arrs arr: df arr v k: if IsList[result[k]] & IsList[v]: ; if both valures are lists result[k] = fn.this[result[k] v] else: result[k] = v var IsList = fn x: ret x?.IterList != nil ][array array1 ....] ; array_replace($array, $array1, ....) fn[x arrs...: mut result = x df arrs arr: df arr v k: result[k] = v ][array array1 ....] ; array_reverse($array) ; don't preserve keys array.keys.sort[-k] ; reverse keys array.sort[k] ; then rekey ; array_reverse($array, true) ; preserve keys array.sort[-k] ; assuming keys are currently in order array.keys.sort[-k] ; no assumptions ; array_search($needle, $haystack, true) haystack[[v == needle]].keys[0] ; array_shift($array) var result = array[0] array.rm[0] result ; array_slice(array, offset, length, false) ; don't preserve keys array[[(offset..(length-1)).has[k]]].sort[k] ; array_slice(array, offset, length, true) ; preserve keys array[[(offset..(length-1)).has[k]]] ; array_splice($array, $offset, $length) var condition = fn k: (offset..(length-1)).has[k] var result = array[[condition]] array.rm[[condition]] ; array_splice($array, $positiveOffset, $positiveLength, $replacements) df array[[(positiveOffset..(positiveLength-1)).has[k]]] v k c: v = replacements[c] ; array_splice($array, $negativeOffset, $negativeEnd, $replacements) df array[[((array.len-negativeOffset)..(array.len-negativeEnd)).has[k]]] v k c: v = replacements[c] ; array_sum array[v+v2] ; array_udiff_assoc($array1, $array2, ...., $compare) array1[[ !( array2[[k2 v2: k==k2 & compare[v v2] ]].join[a&b] $ ....) ]] ; array_udiff_uassoc($array1, $array2, ...., $dataCompare, $keyCompare) array1[[ !( array2[[k2 v2: keyCompare[k k2] & dataCompare[v v2] ]].join[a&b] $ ....) ]] ; array_udiff($array1, $array2, ...., $compare) array1[[ !(array2[[v2: compare[v v2] ]].join[a&b] & ....) ]] ; array_uintersect_assoc($array1, $array2, ...., $compare) array1[[ array2[[v2 k2: k==k2 & compare[v v2] ]].join[a&b] & .... ]] ; array_uintersect_uassoc($array1, $array2, ...., $dataCompare, $keyCompare) array1[[ array2[[v2 k2: dataCompare[k k2] & keyCompare[v v2] ]].join[a&b] & .... ]] ; array_intersect($array1, $array2, ...., $dataCompare) array1[[ array2[[v2: dataCompare[v v2] ]].join[a&b] & ..... ]] ; array_unique(array) array.sort[v][[!({v}<s)]] ; array_unshift(array, var1, var2, ....) array.=ins[0 {var1 var2}] ; array_values(array) array.map[v c] ; array_walk_recursive var array_walk_recursive = fn inputs func userData: var walk = fn value: if value.len == nil: func[v k userData] else: df aList v k: func[v k userData] walk[inputs] ; array_walk(array, callback, other) array.=map[callback[v k other] k] ; array(....) {....} ; arsort(array) array.keys.sort[-array[v]] ; integers array.keys.sort[array[v]].sort[-k] ; anything ; asort(array) array.keys.sort[array[v]] ; compact(varname, ...) fn.this[[{varname ....}.has[k]]] ; count(array) array.len ; extract(var_array) private mix varArray ; in_array(needle, haystack) {needle} < haystack ; krsort(array) array.keys.sort[v].sort[-k] ; ksort(array) array.keys.sort[v] ; list(a, b, c) = array('a','b','c') {a b c} = {'a' 'b' 'c'} ; natcasesort(array) array.map[v.lower].fsort[v.natcmp[v2]<0] ; natsort(array) array.fsort[v.natcmp[v2]<0] ; range(low, high, step) (low/step...high/step){[v*step]} ; prev, pos, current, next, reset ;[ function testArrayIteratorfunctions() { $array = array('step one', 'step two', 'step three', 'step four'); // by default, the pointer is on the first element echo current($array) . "
    \n"; // "step one" next($array); // skip two elements next($array); echo current($array) . "
    \n"; // "step three" prev($array); // rewind one echo current($array) . "
    \n"; // "step three" // reset pointer, start again on step one reset($array); echo current($array) . "
    \n"; // "step one" } ;] testArrayIteratorfunctions = fn: cur cur=0 var array = {'step one' 'step two' 'step three' 'step four'} var i = array.iterList ; grab array's iterList, to iterate in the same order df would ; echo first element logger.i[array[i[cur]] '
    '@] cur+=2 ; skip two elements logger.i[array[i[cur]] '
    '@] cur-- ; rewind one logger.i[array[i[cur]] '
    '@] cur = 0 ; reset index, start again on 'step one' logger.i[array[i[cur]] '
    '@] ; rsort(array) array.sort[-v] ; numbers array.fsort[!fn[ret v < v2][v v2]] ; strings ; shuffle(array) array.sort[rand[]] ; sizeof(array) array.len ; sort(array) array.sort[v] ; uasort(array, cmp_function) array.keys.fsort[cmpFunction[array[v] array[v2]]] ; uksort(array, cmp_function) array.fsort[cmpFunction[k k2]] ; usort(array, cmp_function) array.fsort[cmpFunction[v v2]]
  55. ; intersperse '.' "MONKEY" ; intersperse 0 [1,2,3,4,5,6] "MONKEY".join[a.cat['.' b]] {1 2 3 4 5 6}.join[{} a.cat[{0 b}]] ; intercalate " " ["heyyy","youuu","guyyyyyys"] ; intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]] {"heyyy" "youuu" "guyyyyyys"}.join[a.cat[" " b]] ; notice that this is the same form as is used for string intersperse {{1 2 3} {4 5 6} {7 8 9}}.join[{} a.cat[{0 0 0 b}]] ; transpose [[1,2,3],[4,5,6],[7,8,9]] {0..00}.map[k: {{1 2 3} {4 5 6} {7 8 9}}.map[v[k]] ] ; fold (+) [1,2,3,4,5] {1 2 3 4 5}.join[a+b] ; concat ["foo","bar","car"] ; concat [[3,4,5],[2,3,4],[2,1,1]] {"foo" "bar" "car"}.join[a.cat[b]] {{3 4 5}{2 3 4}{2 1 1}}.join[a.cat[b] ; concatMap function aList aList.map[function[v]].join[a.cat[b]] ; and [True, False] {true false}.join[a&b] ; or [True, False] {true false}.join[a$b] ; any (==4) [2,3,5,6,1,4] {2 3 5 6 1 4}.any[v==4] ; all (>4) [6,9,10] {6 9 10}.all[v>4] ; iterate (*2) 2 {1..00}.map[v*2] ; splitAt 3 "heyman" var x = "heyman" {x[[k<3]] x[[k>3]]} ; takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1] ; takeWhile (/=' ') "This is a sentence" {6 5 4 3 2 1 2 3 4 5 4 3 2 1}.split[{3}][0] "This is a sentence".split[" "][0] ; dropWhile (/=' ') "This is a sentence" " ".cat["This is a sentence".split[" "][[k>0]].join[a.cat[" " b]]] ; span (/=' ') "This is a sentence" var x = "This is a sentence" {x.split[" "][0] " ".cat[x.split[" "][[k>0]].join[[a.cat[" " b]]]]} ; sort [8,5,3,2,1,6,4,2] {8 5 3 2 1 6 4 2}.sort[v] ; group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] mut newList = {} df {1 1 1 1 2 2 2 2 3 3 2 2 2 5 6 7} x: if x!=newList.sort[k][0][0]: newList.=ins[{}] newList[newList.len-1].=ins[x] newList ; map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] {1 1 1 1 2 2 2 2 3 3 2 2 2 5 6 7}.group[v].map[{v[0] v.len}] ; inits "w00t" mut result = {""} df "w00t" x: result.=ins[result.sort[-k][0],x] result ; tails "w00t" mut result = {"w00t"} mut last = fn: return result.sort[-k][0] while last[].len > 0: mut next = last[] next.=rm[0] result.=ins[next] result ; "cat" `isInfixOf` "im a cat burglar" "im a cat burglar".find["cat"].len > 0 ; "hey" `isPrefixOf` "hey there!" "hey there!".find["hey"].any[v.keys.has[0]] ; "there!" `isSuffixOf` var x = "oh hey there!" x.find["there!"].any[v.keys.has[(x.len-1)]] ; partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy" var result = "BOBsidneyMORGANeddy".group[('A'..'Z').has[v]] {result.true result.false} ; find (>4) [1,2,3,4,5,6] {1 2 3 4 5 6}[[v>4]][0] ; 4 `elemIndex` [1,2,3,4,5,6] {1 2 3 4 5 6}[[v==4]].k[0] ; ' ' `elemIndices` "Where are the spaces?" "Where are the spaces?"[[v==' ']].keys ; findIndex (==4) [5,3,2,1,6,4] {5 3 2 1 6 4}[[v==4]][0].keys[0] ; lines "first line\nsecond line\nthird line" "first line"@,"second line"@,"third line".split[""@] ; unlines ["first line", "second line", "third line"] {"first line" "second line" "third line"}.join[a,@b] ; words "hey these are the words in this sentence" "hey these are the words in this sentence".split[" "] ; unwords ["hey","there","mate"] {"first line" "second line" "third line"}.join[a," ",b] ; delete 'h' "hey there ghang!" var x = "hey there ghang!" x.rm[ x[[v=='h']].k[0] ] ; [1..10] \\ [2,5,9] ??? ; [1..7] `union` [5..10] 1..7 $ 5..10 ; [1..7] `intersect` [5..10] 1..7 & 5..10 ; insert 4 [3,5,1,2,8,2] var x = {3 5 1 2 8 2} var y = 4 x.ins[x[[v>y]].k[0] y] ; why would you want to make this a core function in your language??? ; Map.fromList [(1,2),(3,4),(3,2),(5,5)] {{1 2}{3 4}{3 2}{5 5}}.map[{v[0]:v[1]}] ; Map.empty {} ; Map.insert 3 100 Map.empty {}[3][100] ; Map.null Map.empty {} == {} ; duh ; Map.size Map.empty {}.len ; Map.singleton 3 9 {3:9} ; lookup 3 Map.fromList [(1,2),(3,4),(3,2),(5,5)] {{1 2}{3 4}{3 2}{5 5}}[3] ; Map.member 3 $ Map.fromList [(1,2),(3,4)] {{1 2}{3 4}}.keys.has[3] ; Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)] {1:1 2:4 3:9}.map[v*100] ; Map.filter (>4) $ Map.fromList [(1,1),(2,4),(3,9)] {1:1 2:4 3:9}[[v>4]] ; Map.toList . Map.insert 9 2 $ Map.singleton 4 3 {4:3}.map[{k v}] ; keys Map.fromList [(1,1),(2,4),(3,9)] {1:1 2:4 3:9}.k ; elems Map.fromList [(1,1),(2,4),(3,9)] {1:1 2:4 3:9}.v ;[ phoneBook = [("betty","555-2938") ,("betty","342-2492") ,("bonnie","452-2928") ,("patsy","493-2928") ,("patsy","943-2929") ,("patsy","827-9162") ,("lucille","205-2928") ,("wendy","939-8282") ,("penny","853-2492") ,("penny","555-2111") ] Map.lookup Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) phoneBook ;] var phoneBook = {{"betty" "555-2938"} {"betty" "342-2492"} {"bonnie" "452-2928"} {"patsy" "493-2928"} {"patsy" "943-2929"} {"patsy" "827-9162"} {"lucille" "205-2928"} {"wendy" "939-8282"} {"penny" "853-2492"} {"penny" "555-2111"} } phoneBook.group[v[0]].map[ v.join["" a,", ",b[1]] ]
  56. Tho some of these things would be better to be done in more than one line, this demonstrates how Lima uses some basic constructs to describable an infinity of possibilities. Going through the equivalents for Python's itertools library functions:

    ; count(10, 2) (10..00)[[v%2==0]] ; cycle([1,2,3,4]) {1 2 3 4}*00 ; repeat(10) {10}*00 ; chain([1,2,3], [4,5,6]) {1 2 3}.cat[{4 5 6}] ; compress([1,2,3,4], [1,0,1,0]) {1 2 3 4}[[{1 0 1 0}[k]]] ; dropwhile(lambda x: x<5, [1,4,6,4,1]) var x = {1 4 6 4 1} var elements = x[[!(v<5)]] ; elements that don't match the criteria v<5 x[[ k>=elements.keys[0] ]] ; groupby(alist lambda x: x%4) alist.group[v%4] .map[v.sort[k]] ; to get rid of the key preservation that python doesn't have ; ifilter(lambda x: x%2, [10,11,12,13]) {10 11 12 13}[[v%2]] ; ifilterfalse(lambda x: x%2, [10,11,12,13]) {10 11 12 13}[[v%2 == 0]] ; islice([1,2,3,4], 2, 100, 4) {1 2 3 4}[[ k in (2..100)[[k%2==0]] ] ; imap(func, list, list2) list.map[func[v list2[k]]] ; starmap(func list) list.map[func[v[0] v[1]]] ; tee(alist, 4) (0..3).map[alist] ; makes a list of copies of alist ; takewhile(alist func) df alist v k: if !func[v]: alist.rm[[key: key>=k]] ; izip(alist, anotherList) alist.map[if[anotherList[k] == nil: nil else: {v anotherList[k]}] ; izip_longest(alist, anotherList, fillvalue='-') alist.map[if[anotherList[k] == nil: '-' else: {v anotherList[k]}]
  57. A program that simple takes each line and creates a new file where each line is labeled with line numbers.

    Python

    out = open('uid list'+str(absoluteCount)+'.txt','w') with open('mif_retarget_fbid_12_12_11.csv','r',encoding='utf-8') as f: for line in f: line = lines[n] out.write("Line "+str(n+1)+"'"+line+"'\n") if n%50000 == 0: out.close() out = open('uid list' + str(int(n/50000)+1) + '.txt','w') out.close()

    Lima

    var input = file['mif_retarget_fbid_12_12_11.csv' 'utf-8'] df input.split[''@] line n: file['uid list' n/­/50000+1 '.txt' 'utf-8'].=cat[ "Line " n+1 ": '"line"'"@]

    Because Lima automatically handles file closure when there are no more references to that file, the programmer doesn't have to deal with that. And because Lima will optimize access to the output file, a reference to the file doesn't need to be explicitly stored, and can be conceptually opened on every iteration of the loop (although any reasonable optimization will leave the file open over multiple iterations).

  58. Taking an example, from lua's manual:

    Lua

    function downloadAndPrintLength (host, file) local c = assert(socket.connect(host, 80)) local count = 0 -- counts number of bytes read c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do local s, status = receive(c) count = count + string.len(s) if status == "closed" then break end end c:close() print(file, count) end function receive (connection) connection:timeout(0) -- do not block local s, status = connection:receive(2^10) if status == "timeout" then coroutine.yield(connection) end return s, status end threads = {} -- list of all live threads function get (host, file) -- create coroutine local co = coroutine.create(function () downloadAndPrintLength(host, file) end) -- insert it in the list table.ins(threads, co) end function dispatcher () while true do local n = table.getn(threads) if n == 0 then break end -- no more threads to run local connections = {} for i=1,n do local status, res = coroutine.resume(threads[i]) if not res then -- thread finished its task? table.remove(threads, i) break else -- timeout table.ins(connections, res) end end if table.getn(connections) == n then socket.select(connections) end end end host = "www.w3.org" get(host, "/TR/html401/html40.txt") get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf") get(host, "/TR/REC-html32.html") get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt") dispatcher() -- main loop

    Lima

    downloadAndPrintLength = fn host file: var c = tcp.connect[adr[host 80] 'httpText["utf-8"]'] c.send['GET 'file' HTTP/1.0'@ @] ; sending a request logger.i[file c.len] ; prints the length of the data returned (waits until the connection is closed from the other end, since c.len isn't known until then) get = fn host file: thread: downloadAndPrintLength[host file] main = fn: host = 'www.w3.org' get[host "/TR/html401/html40.txt"] get[host "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf"] get[host "/TR/REC-html32.html"] get[host "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt"]

    The vast difference in size of these implementations comes from Lima's simple thread syntax and automatic optimization (such that a thread may use coroutine-style under the hood), Lima's ubiquitous list syntax that is used for any list-like entity, and implicit futures (like c.len which blocks the execution of that thread until it is known).

  59. fn functionName int[a b]: ret a+b int a: ret fn.this[a 1] ;[ here, if the function is called with only one argument, then b defaults to 1 ;]
  60. ; right stripping spaces and extended spaces var rstrip = fn aString: mut str = aString df str v k: if v == ' ' $ v.name == 'extendedSpace': str.=rm[k] else: break ret str ; left strip var lstrip = fn aString: ret rstrip[aString.sort[-k]] ; right strip the reversed string
    This pattern can be used to strip any type of characters.
  61. Encoding issues
Name:

Think about control characters in strings. What if someone copies ascii into your program - how are you supposed to deal with that? Maybe omit escape characters (with some other way of adding them in), but allow them in strings? Add to the safe traversal operator that if a bracket access throws an exception, it'll return nil For make, rather than getting rid of it and using functions instead, change `make` into something that can be done in user-space by allowing creation of custom meta properties using unique ids. So if a module that defines something like make wanted to add a property to an object's meta data, you would create an ID object (that has a hashcode dependent on its identity and compares against its identity) and use that as the key for the meta data. For example: makeMetaInfoKey = IdObject ; If another module wants to use the data stored in this key, ; they'd need a reference to this object makeFn = fn constructor: var mobj = meta[callingScope['this']] if !mobj.info.has[makeMetaInfoKey]: mobj.info[makeMetaInfoKey] = {var[originalBracketOp originalExclamationOp]} else: ; In this case, maybe have some easier way to access the super constructor from the new constructor var info = mobj.info[makeMetaInfoKey] info.originalBracketOp = mobj.operators['['] info.originalExclamationOp = mobj.postOperators['!'] mobj.operators['['] = constructor mobj.postOperators['!'] = fn: return { mix[mobj![ operator[ [ ] = info.originalBracketOp postOperator[ ! ] = info.originalExclamationOp ] } meta[callingScope.this].info[makeMetaInfoKey] callingScope.this callingScope Maybe a?[b] should be able to be use to set values too. Eg var a = {} a?[b] = 5 ; a now contains {b::5} a?[b]?[c] = 9 ; a now contains {b::{c::9}} https://blog.kentcdodds.com/write-your-own-code-transform-for-fun-and-profit-140abde9c5c6 Babel plugins have great ideas about code transformation https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-asts The interface operator should probably represent an interface of whatever the object holds at the time the type is checked, rather than sealing the type based on what the object held when the operator was called. One major reason for this is recursive types, or object where its properites might contain a value with the interface of the object itself (eg `this!`). optimizers should be created from code transformers. A code transformer knows how to do a particular transformation, but has no logic about when that transformation is appropriate. An optimizer should use an analyzer to collect the information it needs, the optimizer should then decide which transformations to do, and then do them. Maybe optimizers should be written to take in one new piece of information at a time, so that they're written to run well on incremental changes (vs from-scratch rebuilds). Would it be possible to write a transformer to transform from and to a high level language, and then convert the transformer to transform to and from a different (lower level) langauge? If so, it could make optimizers much easier to write. Use :> to indicated default arguments so destructuring assignments and parameter lists can look the same attribute declarations (with types - must be public and at the top-level in a module) Consider adding an operator that turns normal functions into "pipeline" form. For example: a[] -> b[x] -> c[] -> d[1 2 3] would be the same as: d[c[b[a[] x]] 1 2 3] So A -> B[...] would be transformed into B[A ...] Alternatively, it A -> B ... could be transformed into B[A ...], which would make the above example: a -> b x -> c -> d 1 2 3 How bout | (and use || for nil coalescence) a | b | x | c | d 1 2 3 Piping into a different parameter than the first: a |2 b 1 |3 c 1 2 4 5 |'paramName' fnWithOptionals == Documentation structure == Rethink the intro using http://cone.jondgoodwin.com/coneref/showcase.html think more about the 'inputs', 'keyboard', and 'touch' objects and how they relate. Maybe separate out most the encodings stuff into a separate module in the standard library Add an example (to the intro) of defining and using a DSL with `macro`. Eg a parsing DSL that defines how to parse and unparse a character encoding or file format. Make it more clear which parts of the documentation are an exhaustive reference and which are guides. == Optimization == Add to the an example of `optimize` and other features like that (input expectations, performance tradeoffs) * optimize needs to have a way to specify things like "optimize this for the 80% most likely inputs" == concepts == Write a concept about dependency injection using attributes. probably write a concept about list `len` and how to get a list's current length while its building using asynchronous code (`thread`). Write a more involved concept on optimize[] that explains what it does and examples of when it would be useful. Try to do an end to end example with optimizer-module code. Write a concept on how to do something like observee.info(someValue).set(...) using an attribute. == Types == Maybe extend cast to somehow work on all types. Cast would constrain the type of an object to something, so it could be set to a variable with a more constrained type. (see cast in stanza) Think about how you might implement implicit generics. Literals are a problem here, but you could do inferred typing. typeParam T addToList = fn list[T] theList T theItem: ; The idea is T must match, but how is that matching done? theList.ins[theItem] Should types in a function's parameter list only be evaluated when the function is executed? If its done that way, it would mean that you could use the interface of the object the method is contained in as a type inside the method parameters. Maybe have a way to set a typing system for a particular file/module/project. This would be configured in package.lima and constrain the relevant source files to using only types that are subsets of types in a configured set of types. nim's distinct types seem pretty useful for preventing spaceships from crashing Pony and rust both have ways to specify ownership of a reference to a value and mutability plays a role in what's allowed. Think about how you can specify that kind of thing in Lima. Think about having the possibility of type data, where types can pass around data in certain circumstances. This could be used to replicate the ownership models of pony and rust. === How will types work? === Types matter in a couple key circumstances: * When assigning a value to a variable * When passing values into a function * When returning values from a function A type, at its core, is a function that checks if a value matches the conceptual type. When a variable is set or when a variable is returned from a function, the value's type is checked. When a variable is passed into a function as an argument, the function's dispatch list has type checking functions that check the arguments to see which dispatch list matches. Note that type macros have to have kinda weird functionality to properly support being used in parameters. They have to respect an attribute that tells them to go into "parameter mode" and not evaluate default values, which need to be deferred until its decided the parameter set is the matching one. === variance === in one of the concepts there's an unfinished thought about variance: https://haxe.org/manual/type-system-variance.html . It seems like in lima you shouldn't normally have to worry about covariance. But here's a case where you would: list[int] a = {1 2 3} ref[list[fra]] b ~> a ; totally fine, since a list[int] is also a list[number] ref[list[complex] b.=ins[4.4] ; fails because the value b points to (a) is a list[int] and can't contain non-integer values like 4.4 Here's another similar case: list[int] a = {1 2 3} f[a] const f = fn list[fra] x: x.=ins[4.4] There are a couple problems with this: 1. The obvious - you can get sometimes a runtime error when inserting into something you thought was a list of that type (when its really a reference to a supertype) 2. Less obvious - you can't correctly type a reference to a supertype when you want to point it to a list of subtypes for writing only. Here's an example of that list[fra] a = {1.1 2.2 3.3} ref[list[int]] b ~> a ; errors because a contains values that aren't fra values b.=ins[3] ; even tho all b wants to do is insert integers There are a couple solutions to this: A. Don't do anything - allow the runtime error and leave it at that B. Require (or maybe just allow) a reference type to specify its variance https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) C. Disallow reference types entirely (actually this isn't a solution - nix it) ref[const[list[fra]]] ; const only allows reading ref[writeonly[list[int]]] ~> {1.1 2.2 3.3} ; writeonly maybe ignores types (so the type is just documentation?) ref[const[list[fra]] x ~> {1 2 3} // is there a good way to make this succeed ref[list[fra]] x ~> {1 2 3} // but this fail? There is only if there is a defined subtype-supertype relationship. Is it possible to always have a well defined relationship? No its not, if you allow stuff like type.cond where there may not be any known way to determine all the values that type.cond will return true for. For types where a subtype-supertype relationship is unknown, the compiler probably should just emit a warning and otherwise just treat it as a var type for the purposes of static type analysis. Lima won't have the ability to always be 100% type safe. In the variance debate, stanza takes the stand that covariance is the usual use case (reading) and that the type-checker won't yell about contravariance until a case of writing to an array of the wrong type actually occurs. Perhaps Lima should assume covariance unless the parameter is tagged with the writeonly type. == var might need to be a superclass type of everything == In stanza, var is both a subclass of everything and a superclass of everything (maybe? they say so here: http://lbstanza.org/optional_typing.html). In lima, it depends on whether or not we want this case to be caught by a type-checker or not: var x = fn writeonly list[var] a: a.=ins['hi] list[int] alist = {1 2 3} x[alist] ; this will/can be caught by a static typechecker even if alist's elements can't be determined at compile- time It seems like maybe we would want var to be a subclass and superclass of everything (except nil). There's actually a more important reason to do this. If you want to do optional typing that makes it easy to incrementally add types, you need to be able to pass in a typed variable into a function that expects a var: var f = fn var a: doSomething[a] int x f[x] ; this shouldn't get stopped by a type checker === make === How is something like `make` inherited or overridden? If you inherit from an object that has defined `make` and you want to override it, what do you do? If you don't mix in the bracket operator, the object returned by your new `make` won't have the originally defined bracket operator. Maybe store the original bracket operator in a property called `make` so an overriding `make` can grab it when it knows its overriding (which it could know if an `override` macro was used but not if the operator simply wasn't mixed in. Maybe make needs to override the interface operator so that the interface will have the correct args for the bracket operator that make overrides. == other == How does atomic work when it needs input or output? You might need a way of describing when an object used in two different atomic statements doesn't conflict, and when two objects used by two different atomic statements *do* actually conflict for optimization purposes in the first case, and safety purposes in the second case. For example, if two atomic statements are talking to the same server. Think about how to deal with arb in dev vs prod, and how to make sure you can repro bugs from someone else's machine in the face of behavior that switches based on hardware performance metrics (that might even be taken real-time at runtime). Maybe have some record of what arb choices are made and be able to use that record to force arb choices in another build. Module loading shouldn't cause circular dependencies unless there's an actual circular value dependency. This might require the use of implicit futures or something. But for example, this should be totally fine: Module A: Module B: use['./b'] use['./a'] a1 = 5 b1 = 1 a2 = fn: b2 = fn: ret b1 ret a1 While this should actually throw an exception: Module A: Module B: use['./b'] use['./a'] a1 = b1 b1 = a1 Revisit "Why Lima doesn't need devDependencies and optionalDependencies"
Last Updated: 2018-09-03