# Mesa Language Manual ## Table of Contents 1. [Introduction](#introduction) 2. [Principles](#principles) 3. [Lexical Structure](#lexical-structure) 4. [Values](#values) 5. [Procs](#procs) 6. [Types](#types) 7. [Protocols](#protocols) 8. [Expressions and Operators](#expressions-and-operators) 9. [Bindings and Scope](#bindings-and-scope) 10. [Control Flow](#control-flow) 11. [Error Handling](#error-handling) 12. [Modules](#modules) 13. [Packages](#packages) - [Appendix A - Grammar](#appendix-a---grammar) - [Appendix B - Built-in Behaviors](#appendix-b---built-in-behaviors) - [Appendix C - Built-in Errors](#appendix-c---built-in-errors) - [Appendix D - Operator Precedence](#appendix-d---operator-precedence) ## 1. Introduction This document describes the design & behavior of the Mesa programming language. Mesa is a hobby programming language designed primarily for writing personal software including web applications and command-line utilities. It is inspired by Ruby, JavaScript, and Swift (among others). It is dynamically & strongly typed but features a subset of compile-time semantic analysis which can achieve some of the correctness assurances of more sophisticated static type systems. It features a pragmatic data model taking cues from object-oriented as well as functional approaches. ## 2. Principles Mesa's design is guided by a set of principles. No one principle dominates over any other, and they are traded off against one another where appropriate. More importantly, they are not to be understood as a set of *axioms* from which the entire design follows; rather, they are guidance to be used for evaluating points of tension or directions of development. A language which is axiomatic may possess a satisfying simplicity, but this satisfaction comes at the cost of its expressiveness. These principles are furthermore not to be taken as ultimate or universal, but particularly suited to the design of personal software systems. **Systems are built from the coherent organization of concepts and relationships.** Programs represent discrete understandings of problems and solutions in a particular knowledge space. Thus, a programming language must provide the tools for constructing a vocabulary and grammar appropriate to the problem domain. In Mesa, systems are composed of a foundation of nouns and verbs which are combined in distinct forms of relationships, making the task of describing a particular problem the same task as converting it to an appropriate meta-language. **Prioritizing local reasoning results in clarity and encourages composability.** Whereas some systems value flexibility above all else, Mesa prioritizes the ability to examine a program in relative isolation and, with a set of rules & restrictions in mind, understand what it means. Every name binding is typically locatable within the current source file. Standard control flow constructs and bounded points of extensibility provide specific ways to describe a problem space. The result is that reading and understanding a particular program consists of converting a limited set of language constructs into a defined set of domain constructs and exploring the resulting space. **Modeling complexity necessitates navigating it rather than refusing it.** Mesa does not claim to be a 'simple' language but nevertheless attempts to be an understandable one. Programming languages, like natural languages, are tools for expressing thought and denoting reasoning, and for this reason must bend to the facts of the material world. While there is a satisfying reassurance in the belief that a minimal system can emphasize only the relevant aspects of a particular problem—while the language used to denote the problem regresses into the background—the reality is that the problem itself ultimately resolves into language. The world is not, despite our efforts to make it so, a coherent system, and so our tools for describing it must not resign to this assumption. **Optimizing for small scale yields less generality**: A concept which provides affordances at a small scale may not necessarily provide the same affordances at a larger scale, and vice-versa. In particular, industrial systems tend to require robustness or universality that is not applicable at the scale of personal systems. By optimizing for one end of this tradeoff as opposed to attempting to satisfy both, programs written in Mesa can satisfy the hobbyist without burdening them with constraints which may be legitimate in an industrial context. **Visual cohesion is not separate from conceptual cohesion.** Programs are both text files on disk and the reasoning which those files denote. Just as coherent concepts establish relations to one another with specific meanings and patterns, so must the text representing those concepts possess harmonious relationships. A resonant Mesa program strives to possess a distinctive style, rhythm, and rhyme just as a resonant piece of writing does. **Understanding and trust are the foundations for learning and empowerment.** A programming language designed for building personal software ultimately serves both those who use it and those in their community with whom the software is shared. Mesa provides features for introspection and limits its available forms of access control so as to make the software explainable and understandable to anyone who can access it. Additionally, Mesa itself is free software and its implementation is documented so as to make building it and extending it as easy as possible. ## 3. Lexical Structure All Mesa source is UTF-8 encoded text. This source text is broken up into primitive elements, called tokens. ### Identifiers An identifier begins with an ASCII letter or `_` and is followed by any number of ASCII letters, digits, and `_`s. It may end with a single `?` or `!`. ```mesa byte read_all _init Account JSON empty? sort! ``` Capitalization carries no requirements to the language. An identifier with a capitalized first letter conventionally names a type or protocol. A trailing `?` or `!` in an identifier carries no requirements to the language. A trailing `?` is conventionally used to name a boolean predicate. A trailing `!` is conventionally used to distinguish a mutating, raising, or otherwise distinct variant of a non-suffixed name. A trailing `!` is treated as part of the preceding identifier unless it is immediately followed by an `=`. In this latter case, it is considered part of the `!=` operator. Mesa has no bare `=` operator, so this is unambiguous. ### Keywords The following symbols are reserved words and cannot be used as identifiers: ```mesa and break case def do each else end export extern false impl import in loop module nil not or proto raise rescue return self then true type when ``` ### Literals A **number literal** is a sequence of digits with an optional fractional part separated by a `.`. The fractional part must be followed by at least one digit. A number literal is never negative; `-5` is unary `-` applied to `5`. There is no digit separator, exponent notation, or alternate radix. ```mesa 17 64.2 3.0 ``` A **string literal** is a sequence of characters delimited by double quotes. It may span multiple lines. A **character literal** is a single character delimited by single quotes. Both string and character literals permit the escapes `\n`, `\t`, `\"`, `\'`, and `\\`. There is no string interpolation. ```mesa "Hello, world!" "Multi line" "Multi\nline" 'a' 'é' '\t' ``` A **boolean literal** is either `true` or `false`. The **nil literal** is `nil`. ### Comments and Whitespace Comments begin with a `#` and run until the end of the line. There is no multi-line comment form. ```mesa # Add the numbers. 5 + 2.3 # These are numbers. ``` Almost all whitespace in a Mesa program is insignificant. Newlines are an exception; they are used to delimit expressions. There is no explicit statement separator or terminator. ### Operators and Punctuation The following tokens are operators or otherwise meaningful punctuation: ```mesa := == != < > <= >= + - * / << & : , . ( ) [ ] { } ``` The assignment operator is spelled `:=`. A bare `=` is a syntax error. ## 4. Values **Values** are the fundamental unit of composition in Mesa. Every name in a Mesa program is bound to an inspectable value with some set of behaviors. Like other dynamically typed languages, values have **types**, and any name can be bound to a value of any type. A value's type functions as the primary descriptor for that value's behavior. A type provides a structure for otherwise arbitrary data, and from this structure emerges a coherent concept which affords composing abstractions. Types have members which are properties about values of that type or behaviors which values of that type exhibit. One can say that values are 'instances of' a particular type, and this will be familiar from object-oriented vocabulary. Instances of a particular type behave in similar ways. ### Built-in Types The following types are built in to the language: - `Num`: A 64-bit floating-point number, e.g. `64.2`, `17`. - `Bool`: A boolean, `true` or `false`. - `Char`: A single Unicode scalar, e.g. `'a'`, `'é'`. - `Str`: An immutable sequence of Unicode scalars, e.g. `"Hello, world!"`. - `Digest`: An immutable, opaque blob of bytes, used for hashing. - `List`: A mutable, heterogeneous, insertion-ordered sequence of values, e.g. `[1, "apple", nil]`. - `Dict`: A mutable, heterogeneous, insertion-ordered mapping of keys to values, e.g. `{"id": 1024}`. - `Proc`: An invocable unit of behavior. A method bound to its receiver is also a `Proc`. - `Type`: A meta-unit of related data and functionality. - `Proto`: A unit of behaviors common to multiple types. - `Module`: A unit of related functionality. - `Nil`: An absent value, `nil`. ### Value and Reference Semantics Mesa supports value semantics for some built-in types. The types which follow value semantics are: `Num`, `Bool`, `Char`, `Str`, `Digest`, and `Nil`. All other types, built-in and user-defined, follow reference semantics. The consequences of this design for *equality* are mitigated by defaulting to structural equality for all types, overridable in cases where it is appropriate. The consequences of this design for *mutability* are not mitigated; Mesa instead encourages mutable algorithms and data sharing as the natural paradigm for expressing domain relationships and the change which occurs over a program's lifetime. ### Truthiness, `nil`, and Absence Every value other than `nil` and `false` is **truthy**. When a truthy value is used in a conditional or with a logical operator, it is equivalent to `true`. An empty list `[]` and an empty string `""` are both truthy. Truthiness denotes *presence*, not *fullness*. `nil`, on the other hand, is used to denote absence. ## 5. Procs A **proc** is a subroutine which computes a value and hands it back to the caller. ```mesa def balance(transactions) total := 0 each transaction in transactions do ... end total end ``` As with all blocks, the value of calling a proc is the value of its last expression. If there is an early `return`, the value is instead the value provided to the `return`, or `nil` if the `return` has no value. ```mesa def find(id, query) record := find_by_id(id) when record == nil then return nil end ... end ``` ### Parameters and Arguments Procs can be declared with or without **parameters**, values which must be provided by the caller to specify the behavior of the proc. The caller provides **arguments** which bind to these parameters. Without parameters, the proc's parentheses are optional. An incorrect number of provided arguments will raise an error. Parameters can either be *required* or *optional*. Required parameters must be provided by a caller while optional parameters can be omitted. Placing optional parameters before required ones is an error at declaration time. An optional parameter can reference any parameter declared before it, and the expression is evaluated every time the proc is called. ```mesa def new ... end def credit(account, amount: 0) ... end new new() credit(account) credit(account, 127.25) ``` When provided by a caller, arguments can either be *positional* or *keyword*. Positional arguments are not accompanied by a label while keyword parameters are. The labels provided by the caller are the *names of the parameters*, so these names are part of the proc's interface. Placing keyword arguments before positional ones is an error. ```mesa def debit(account, amount) end debit(account, amount: 50.13) ``` ### Invocation and Mentioning A declared proc **invokes** when it is referenced rather than resolving to the value of the proc itself. ```mesa def amount 100 end amount # => 100 ``` Invocation is a property of the *declaration*, not of the value. A name introduced by an assignment or by a parameter never invokes; it reads its value as it is, even when that value is a proc. ```mesa def notify ... end def apply(action) action # => def notify; a parameter never invokes end handler := ¬ify handler # => def notify; a local never invokes handler() # calls notify ``` If invocation would leave a required parameter unfilled, it raises an error. ```mesa def credit(account) ... end credit # => runtime error: missing arg 'account' ``` When used with explicit call syntax, automatic invocation is suppressed. This supports using parentheses in all cases without knowing the type of the value that the proc returns. ```mesa def make ... end make # invokes with no arguments make() # calls with no arguments make()() # calls the result of make() with no arguments ``` To take an invocable as a value, the **mention** operator `&` asserts that what it names is invocable and yields it without invoking. Anything that is not invocable raises an error. ```mesa def make ... end make # invokes make &make # takes make as a value &100 # => runtime error: type Num is not invocable ``` On a name that would not have invoked, `&` yields the value unchanged, though the assertion still holds. It can therefore be written wherever the author wants to be explicit, without needing to know whether the name would have invoked. ```mesa handler := ¬ify &handler # => def notify; the local would not have invoked either way ``` Mentioning can be used to pass a proc as an argument to another proc. ```mesa listeners := [] def add_listener(listener) listeners << listener end def on_credit ... end add_listener(&on_credit) ``` Types *are not invocable* and must instead always use explicit call syntax. This syntactic necessity contains semantic information: creating an instance of a type and exercising a behavior of that instance are emphasized as two different operations. ### Proc Literals A proc defined in an expression context is a **proc literal** and evaluates to the callable proc. Literals can be used to store procs in variables or pass procs to other procs. Though such composition is typically accomplished via types, instances, and protocols, passing procs as values can achieve a form of composition and delegation useful for simple situations. ```mesa add := def (a, b) a + b end add(4, 2) # => 6 ``` A proc literal can be used to pass a proc as an argument to another proc. ```mesa amounts := transactions.collect(def (txn) txn.amount end) ``` Unlike a regular proc, a proc literal *does not* automatically invoke. This follows the rule that only declared procs invoke, while locals do not. ```mesa make := def ... end make # => def ... ``` #### Capturing Proc literals can **capture** variables bound in the scope in which they are defined. Locals referenced in the body of a proc literal are captured by value, including `self`. Module-level and prelude names *are not* captured and resolve at call time. ```mesa def adder(lhs) def (rhs) lhs + rhs end end add := adder(5) add(2) # => 7 add(3) # => 8 ``` Locals capture the *value of the binding*, not the *value itself*. Assigning to a captured name inside a proc literal only changes the literal's own copy of the value. This means that for mutable types whose value is a reference, mutating them inside a literal works as expected. This is the case for `List`, `Dict`, and all user-defined types. `Num`, `Bool`, `Char`, and `Str` are not mutable, however, so assigning to a name bound to them will not mutate the captured value. ```mesa def stream(items: []) next := def when items.empty? then nil else items.shift() end end next end next := stream([1, 2]) next() # => 1 next() # => 2 next() # => nil def broken count := 0 counter := def count := count + 1 count end counter end counter := broken counter() # => 0 counter() # => 0 ``` ## 6. Types **Types** are a foundational tool for abstraction in Mesa along with procs. If a proc defines a verb reusable across contexts, a type defines a *noun* reusable across contexts and more easily transferrable as a value. ### Constructors All types feature a single constructor invoked via call syntax. Because constructors follow normal call syntax rules, they can declare defaults for their parameters and callers can use keyword syntax. There are no additional construction or object initialization concepts. Any alternative construction behavior should go in a type-level method. ```mesa Str() # => "" Num() # => 0.0 type Amount(cents) end Amount(105) # => Amount(cents: 105) ``` As opposed to procs and methods, types *are not* invocable. Referencing the name of a type does not automatically invoke its constructor. One always needs to distinguish a type from its instances, whereas one only rarely needs to distinguish an invocable from the result of its invocation. ### User-defined Types While the built-in types provide a foundation for describing computational use-cases, most applications and libraries will want to define their own types specific to their problem domain. **User-defined types** provide a mechanism for declaring types, their behaviors, and their invariants in a coherent, consistent manner. #### Fields Every type has a statically known set of **fields**, pieces of data that compose the type. Fields can be defined either by the constructor or in the type body. Constructor fields are supplied by the caller while body fields are not. ```mesa type Task(label, priority: 0) end Task("Update documentation") ``` Fields defined in the body can reference constructor fields or other body fields declared before themselves. They are evaluated every time a value of the type is constructed. ```mesa type Group(label) labels := [label] end ``` Only constructor fields are printed by the output of `Display`, while body fields are not. All fields are printed by the output of `Inspect`. ```mesa type Group(label) items := [] end Group("Urgent").display # => "Group(label: \"Urgent\")" Group("Urgent").inspect # => "Group(label: \"Urgent\", items: [])" ``` #### Methods Procs attached to a type are its **methods**. Methods are procs bound to an instance of a type, and they follow all the same rules: they can define parameters, are automatically invoked when referenced, and can be mentioned to suppress invocation. ```mesa type Account(transactions) def balance each transaction in transactions do ... end end end balance := &account.balance ``` In the body of a method, `self` is bound to the instance on which the method was called. ```mesa type Task(label) def clone self end end Task("Refactor").clone # => Task(label: "Refactor") ``` ##### Type-Level Methods A type can define methods which are bound to the type object itself rather than instances of it. This can be done by defining a method with the prefix `self.` in the type body. In the body of a type-level method, `self` is bound to the type object itself. There is *no equivalent* for type-level fields. ```mesa type Amount(cents) def self.zero self(0) end end ``` #### Members and Access Fields, methods, local types, and variants (discussed below) are all uniformly **members** of a type. Accessing a member uses syntax dependent on context: inside the type body, the *bare name* of a member accesses the member; outside the type body, the member operator spelled `.` accesses the member of the instance on the left hand side of the dot. ```mesa type Task(label, priority: 0) def description label end end Task("Documentation").label # => "Documentation" Task("Documentation").description # => "Documentation" ``` Whether or not a member is automatically invoked when referenced follows the normal rules of invocation, which read the declaration and not the value. A method is declared, and invokes. A field is not, and is read as it is even when it holds a proc. ```mesa type Task(label, on_done) def description label end end def archive ... end task := Task("Documentation", &archive) task.description # => "Documentation"; a method invokes task.on_done # => def archive; a field never invokes task.on_done() # calls archive &task.description # takes the bound method as a value &task.on_done # => def archive; the field would not have invoked either way ``` Fields are therefore uniform with one another: a field is read the same way whatever it holds. Uniform access between a field and a method taking no arguments is preserved, since both still yield a value. Mesa has *no access control* mechanism within types. Every member of a type—field, method, local type, variant—is public and introspectable, even if it is not providable by the caller or has no meaning outside of the type's internal implementation. Mesa also has *no mutability control* mechanism. Every field is readable and writable from every part of the program. Methods are the one exception to this rule because they cannot be reassigned. ### Local Types Some types are more related than shared module membership or source code adjacency would imply. In the case of certain domain types, several internal types may be related implementations of similar behavior. For these cases, **local types** provide a form of namespacing within a particular module. ```mesa type LinkedList head := nil type Node(data) end end ``` Local types are not related to the enclosing type in any way other than their name. They cannot automatically see into fields of instances of the enclosing type and cannot call methods on it without explicitly holding an instance. In all ways except for name lookup they function exactly as a sibling type at the module level with a name prefix. ### Case Types **Case types** are Mesa's form of sum types. They are used to represent values which can come in one of a limited number of forms, called **variants**. A variant is declared inside the body of the case type and can declare fields and methods of its own. A variant *cannot* declare its own variants; there is a single level of nesting. In situations where you need multiple levels of nesting, you can nest instances of further case types as fields of the variants. ```mesa type Transaction case Credit(amount) end case Debit(amount) end end ``` References to variants always use the variant's fully-qualified name. This applies in every context: constructing a variant, matching one in `when...case`, and naming one in the case type's own body. ```mesa def absolute when transaction case Transaction.Credit then transaction.amount case Transaction.Debit then -1 * transaction.amount end end ``` Case types support a limited form of behavioral sharing. When a member name cannot be resolved against a case type's variant, the member is looked up against the case type itself. This is suited for situations in which a common behavior can be implemented in terms of a shared behavior. ```mesa type File case Virtual(contents) def read ... end end case Disk(fd) def read ... end end def read_all each byte in read do ... end end end ``` Fallback applies only to *methods, not fields*; a case type cannot have fields. ## 7. Protocols **Protocols** define behaviors shared across different types. Protocols provide a mechanism for types to explicitly declare that they share a common interface across all implementers. A type's conformance to a protocol is statically checked at declaration time. Protocols are named after the primary verb which they provide, rather than with a capability suffix like '-able.' ```mesa proto Draw def draw end end type Square impl Draw def draw ... end end type Circle impl Draw def draw ... end end ``` A protocol can declare both *required* and *provided* methods. A required method must be implemented by the implementing type. A provided method may be implemented by the implementing type, but otherwise falls back to the implementation provided by the protocol. A protocol may not declare, but may possess, *derived* methods. A derived method has the same fallback mechanism as a provided method; however, instead of the default implementation being provided in the protocol declaration, it is provided by the language itself. The protocols which have derived methods are `Equal`, `Hash`, `Access`, and `Inspect` (see below). A protocol must either be empty, contain only derived methods, or contain at least one required method. A protocol with only provided methods is not allowed. ```mesa proto Handle end proto Read def read end def read_all each byte in read do ... end end end type File impl Handle, Read def read ... end end type Socket impl Handle, Read def read ... end end ``` A protocol's provided methods can only access members the protocol *explicitly declares*. This is statically checked at declaration time. ### Built-in Behaviors Some protocols are provided which offer a limited mechanism for user types to hook into built-in behaviors, including some operators. The protocols and the behaviors they govern are as follows: | Protocol | Governs | Status | |-----------|---------------------------|------------------------| | `Equal` | `== !=` | Automatic, overridable | | `Hash` | Use as a `Dict` key | Opt-in | | `Order` | `< <= > >=` | Opt-in | | `Access` | `a[k]` and `a[k] := v` | Opt-in | | `Append` | `<<` | Opt-in | | `Iterate` | Use in `each`1 | Opt-in | | `Display` | User-facing output | Opt-in | | `Inspect` | Developer-facing output | Automatic, overridable | 1. `Iterate` pairs with the `Advance` protocol, not listed here. The details of these protocols, including what they require, how they behave, and what they guarantee can be found in Appendix B. ## 8. Expressions and Operators Mesa is an expression-oriented language. Everything in an expression position evaluates to a value. Not every piece of syntax can be used in expression position, however; declarations like `module` and `type` are not expressions and do not evaluate to a value. Additionally, not every expression evaluates to a *useful* value. For example, `each` evaluates to `nil` unless it `break`s with a value, and `when` evaluates to `nil` if there is no matching branch and no `else` clause. ### Expression Termination Expressions are terminated at a newline when two things are true at once. First, the token *preceding* the newline must be able to end an expression. Second, the token *following* the newline must not be able to continue one. The tokens that can end an expression are: an identifier; the keywords `self` and `end`; a string, character, number, boolean, or nil literal; and the characters `)`, `]`, and `}`. The consequence of this rule is that almost any expression can be wrapped across lines by breaking either before or after an operator. ```mesa transactions. collect(def (transaction) transaction.amount end). select(def (amount) amount.cents > 0 end) # or transactions .collect(def (transaction) transaction.amount end) .select(def (amount) amount.cents > 0 end) ``` ```mesa when shape.visible? and not shape.color.transparent? then ... end # or when shape.visible? and not shape.color.transparent? then ... end ``` The only time one must specifically break before an operator is with `-`, `[`, and `(`. ```mesa 5 - 4 # evaluates to 1 5 # evaluates to 5 - 4 # evaluates to 4; ignored ``` ```mesa items[0] # evaluates to items[0] items # evaluates to items [0] # evaluates to [0]; ignored ``` ```mesa make(1) # evaluates to the result of calling make(1) make # evaluates to the result of invoking make (1) # evaluates to 1; ignored ``` `return` and `break` are the exceptions to this rule. These constructs always terminate at a newline despite the fact that they cannot end an expression. This means a `return` or a `break` is always bare unless its operand starts on the same line. ```mesa return 5.2 # returns 5.2 return # returns nil 5.2 # ignored ``` Additionally, because procs have optional parameter lists, a newline immediately after the name of the proc always indicates an empty parameter list, even if that line begins with a `(`. This is to distinguish parameter lists from grouping expressions with `(`. ```mesa # draw takes 1 parameter, 'shape' def draw( shape ) ... end # draw takes no parameters def draw (shape) end ``` ### Block Results A block of expressions always evaluates to the value of its last expression. The only exception to this is if the block exits early. ### Operators Mesa supports the following operators, grouped by function: **Arithmetic**: `+ - * /` and unary `-` on `Num`. Not overloadable by user types. **Concatenation**: `+` on `Str`. Not overloadable by user types. **Mixing**: `+` on `Digest`. Combines two hashes in an *order-sensitive* manner, i.e. `a.hash + b.hash` and `b.hash + a.hash` are not necessarily equivalent. Not overloadable by user types. **Ordering**: `< <= > >=` on any type implementing `Order`. `Num` compares numerically, `Str` and `Char` compare lexicographically by scalar value. User types can overload by implementing `Order`. **Equality**: `==` and `!=` on any type implementing `Equal`. Compares by *structural equality* over all fields; there is *no reference equality* operator. Automatic for every user type, but can be overloaded by explicitly implementing `Equal`. Implemented on every built-in type. **Logical**: `and` and `or` on any type short-circuit and return the selected operand. `not` on any type always returns a `Bool`. Every value is truthy except for `nil` and `false`, including e.g. an empty `List`. Not overloadable by user types. **Append**: `<<` on a type implementing `Append` appends the operand to the end and returns the receiver so it can be chained. User types can overload by implementing `Append`. Implemented on built-in `List`. **Mention**: `&` on an *invocable* takes its value instead of automatically invoking it. Only applicable to `Proc`s, which includes a method bound to its receiver. ### Call, Member, and Access **Call** syntax uses `(` and `)` to denote arguments passed to a `Proc`, bound method, or type constructor. Calls can pass both positional and keyword arguments, and parameters can be declared required or optional. **Member** access syntax uses `.` to denote accessing a field, method, or local type / variant of the left-hand value. **Access** syntax uses `[` and `]` to denote keyed or indexed access into a collection implementing `Access`. ### Precedence and Grouping Expressions may be wrapped in `( )` to form explicit groups. Otherwise, expressions are parsed according to the following precedence table ordered from highest to lowest precedence: | Operator | Description | Form | Associativity | |-----------------|--------------------------|----------------------|---------------| | () [] . | Call, access, member | Postfix | Left-to-right | | & | Invocable mention | Prefix | Right-to-left | | - | Arithmetic negation | Prefix | Right-to-left | | * / | Multiplication, division | Infix | Left-to-right | | + - | Addition, subtraction | Infix | Left-to-right | | << | Append | Infix | Left-to-right | | == != < <= > >= | Comparison | Infix | Left-to-right | | not | Logical negation | Prefix | right-to-left | | or and | Logical or, and | Infix | Left-to-right | | := | Assignment | Infix, place on left | Right-to-left | This table can also be found in Appendix D. ## 9. Bindings and Scope Every name is introduced by a **binding** which associates that name with a value. The constructs which introduce bindings are type declarations, proc declarations, imports, proc and constructor parameters, `each` loop variables, `rescue` bindings, and assignment. Every source location has a **scope** which consists of the bindings introduced both at that point and by the constructs enclosing it. The constructs which introduce a scope are `each`, `loop`, `when`, `do`, `rescue`, and proc bodies. Two kinds of name are introduced by none of these constructs. `self` is a keyword which denotes a value, but it is not a binding and cannot be reassigned. The prelude's names are bound before any program runs. A binding does not outlive the scope which introduced it. A name first bound inside a block is unavailable after it, while an assignment to a name bound in an enclosing scope rebinds that binding rather than introducing a new one. ```mesa count := 0 do count := count + 1 # rebinds the outer binding extra := 5 # a new local binding end count # => 1 extra # => error: name 'extra' is not defined ``` ### Resolution A name is *always* resolved in the following order, falling back to a later tier only if it is not found in an earlier one. > `locals → self's members → module bindings → prelude` **Locals** are the bindings of the current scope and of the scopes enclosing it. Resolution walks outward, beginning from the innermost scope; if the name is not found in any local scope, resolution continues to the next tier rather than raising. **`self`'s members** are reached through the `self` value which a method implicitly possesses: in an instance method, this is the instance; in a type-level method, this is the type. Each of these values has a fixed set of members against which a name is looked up. This lookup happens even for bare names, so e.g. `name` in an instance method on `type Account(name)` refers to the field. Code outside a method has no `self` and skips this tier. **Module bindings** are a module's top-level declarations, top-level assignments, and the names its imports bind. **Prelude** names are available in every module without being imported. The prelude consists of the built-in type names and nothing else. If a name is not found in any tier, an error is raised. ### Assignment Mesa features a single assignment operator, `:=`. The first assignment to a name binds it, and subsequent assignments rebind it. ```mesa sum := 4 + 5 sum := sum + 1 sum # => 10 ``` The `:=` operator always evaluates to the *assigned value*. ```mesa old := 4 new := old := 2 old # => 2 new # => 2 ``` #### Places The left-hand side of the `:=` operator designates a **place**. A place is either an *identifier*, a *member* expression, or an *access* expression; no other syntax is valid as a place for assignment. ```mesa shape := Square(5.2) shape.color := "red" shapes[0] := shape ``` A place is resolved in the same order as any other name. Assigning to a bare name writes to `self`'s member before it creates a local, which is how a method assigns to a field of its instance with no receiver. ```mesa type Account(balance) def deposit(amount) balance := balance + amount end end ``` A name which resolves in no tier is bound as a local in the current scope. There is *no variable declaration syntax*. Reading an undefined name raises, while assigning to one defines it. ```mesa color # runtime error: name 'color' is not defined. color := "red" color # => "red" ``` Not every place accepts assignment. Methods and read-only members cannot be reassigned. ```mesa type Group(items: []) def size items.size end end Group().size := def 5 end # runtime error: member 'size' on type Group is read-only. ``` A module-level declaration claims its name for the whole of the module that declares it, so an assignment cannot displace one. Re-declaring a member is an error at declaration time. ```mesa def make ... end make := make # semantic error: duplicate member 'make' ``` ### Shadowing There is no explicit syntax for shadowing. Only the binding forms which bind a name in a new scope shadow: proc and constructor parameters, `each` loop variables, and `rescue` bindings. ```mesa num := 5 each num in [1, 2, 3] do num # => 1, 2, 3 end num # => 5 ``` Assignment shadows only a prelude name. Where a name resolves to a local, to a member of `self`, or to a module binding, assignment rebinds it in place; where it resolves to a prelude name or to nothing at all, assignment introduces a new binding in the current scope. ```mesa Num # => type Num Num := 5 Num # => 5 ``` ## 10. Control Flow Mesa features two **looping** constructs (`each` and `loop`), a **conditional** construct (`when`), a **grouping** construct (`do`), two **exit** constructs (`return` and `break`), and **error handling** constructs (`raise` and `rescue`). Control enters at the top of a construct, transfers to subroutines, proceeds sequentially through expressions, and leaves the construct either at its end or through a construct that transfers out of it. ### Loops #### `each` `each` loops over each item in any type implementing `Iterate`, binding a single name for each yielded item. The `iterate` method is called on the value provided, and the resulting `Advance` type is called until it is exhausted. `Iterate` is implemented for the built-in `List`, `Dict`, and `Str` types, yielding items, keys, and characters respectively. An `each` expression evaluates to `nil` unless exited with a `break` value. ```mesa each item in [1, "apple", nil] do item # => 1, "apple", nil end each key in {"id": 100} do key # => "id" end ``` #### `loop` `loop` loops infinitely until explicitly exited with `break`. This can replace any alternative loop construct, like `while`, `until`, etc. A `loop` expression evaluates to `nil` unless exited with a `break` value. ```mesa count := 0 loop do when count == 10 then break end count := count + 1 end count # => 10 ``` ### Conditionals #### `when...else` `when...else` evaluates conditions in the order they are listed until one matches. Alternative conditions are specified using `else when` branches, and a catch-all branch using `else`. An `else` branch is not required, in which case the entire construct evaluates to `nil`; otherwise, it evaluates to the value of the branch which matched. The entire construct is closed by a single `end`. A condition matches when the relevant expression evaluates to a truthy value. ```mesa name := when 4 / 2 == 2 then "four" else when 8 / 2 == 4 then "eight" else "other" end name # => "four" ``` #### `when...case` `when...case` matches against the type of a value. Each branch names a type, and the branches are tried in order. There is no destructuring, no binding form, and no type narrowing; once a branch matches, member access in the body proceeds by normal rules. ```mesa type Expr case Ident(name) case Lit(lit) end when expr case Expr.Ident then expr.name case Expr.Lit then expr.lit end ``` The branches need not name variants of a case type. `when...case` performs general type matching against any type. ```mesa def encode(val) when val case Str then ... case Num then ... else ... end end ``` The branches *cannot* match against a case type itself, given that it can never be constructed. Such a branch will never match. ```mesa type Transaction case Credit(amount) end case Debit(amount) end end result := when transaction case Transaction then "transaction" else "other" end result # => "other" ``` `when...case` is *not* a general pattern matching construct. For conditions more complex than type dispatch, `when...else` should be used instead. #### Exhaustiveness Checking Exhaustive coverage of a `when...case` construct is checked at declaration time against the construct's *branches*. If the branches all name variants of the same case type, they are checked for exhaustiveness. If an `else` is provided in this circumstance, the branches *must not exhaustively cover the variants*; this would swallow later additions to the case type. If the branches do not cover the variants, however, an `else` is permitted. If the branches name unrelated types, whether they be case types or not, an `else` is always permitted. If it is absent, the `when...case` evaluates to `nil` if no branch matches. ### `do` `do` groups a sequence of expressions and evaluates to the value of the last one in its body. `do` introduces a new scope, so it can introduce variables that are not visible after its accompanying `end`. ```mesa sum := do total := 0 each num in [1, 2, 3] do total := total + num end total end sum # => 6 ``` `do` can be paired with `rescue` to guard a series of expressions from a raised error. ```mesa do window.draw(shape) rescue case DrawError.OutOfBounds then ... end ``` ### `return` `return` exits a proc early and causes it to evaluate to the value provided to the `return`. If `return` is not provided a value, the proc evaluates to `nil`. In the absence of a `return`, a proc or block evaluates to the value of its last expression. There is no special-cased guard clause; guarding a particular section of a block can be accomplished by a `return` inside a `when`. ```mesa def draw(shape) when not shape.visible? then return end ... return dimensions end ``` `return` inside a proc literal returns from the literal, not the defining proc. ```mesa def visible(shapes) shapes.select(def (shape) return shape.visible? end) end ``` #### `break` `break` terminates the containing `each` or `loop`. If provided a value, the loop evaluates to the value, otherwise `nil`. `break` is statically checked to never occur outside of a loop body; doing so is an error at declaration time. ```mesa first := each num in [1, 2, 3] do break num end first # => 1 ``` `break` inside a proc literal breaks from the inner loop. ```mesa each nums in [[1], [2], [3]] do first := def each num in nums do break num end end first # => 1, 2, 3 end ``` ### `raise` and `rescue` `raise` propagates a value through calls until a `rescue` handles it, or until the program exits. A `rescue` is always paired with a `do`, and it guards the expressions in that `do`'s body. ## 11. Error Handling Mesa represents both programming and system faults using **errors**. Errors are propagated with `raise` and handled with `rescue`. An unhandled error terminates the program and reports the location it was raised from. There are no result types, no error return values, and no error propagation operator. ### Built-in Errors Errors raised by the language itself or by the core library are variants of the `Error` type provided by the core library. The variants of this type, as well as their purposes, are listed in Appendix C. They are used throughout this section whenever `Error` is referenced. ### `raise` `raise` aborts the currently executing construct and propagates the raised value up the call stack until it is handled. ```mesa raise Error.KeyError("key \"id\" not found") ``` Although it is most useful to raise a value of a purpose-built error type, a value of any type can be raised. ```mesa raise "An error occurred." ``` ### `rescue` `rescue` stops an error from propagating if one of its branches matches the error value. If a branch matches, its corresponding `do` evaluates to the last expression in that branch. ```mesa config := {} port := do config["port"] rescue case Error.KeyError then 4000 end port # => 4000 ``` #### Branches `rescue` matches against the type of the raised value. Like `when...case`, each branch names a type, and the branches are tried in order. ```mesa type LedgerError case InsufficientFunds(account) end case InvalidRecipient(id) end end do account.transfer(amount, to: other) rescue case LedgerError.InsufficientFunds then ... case LedgerError.InvalidRecipient then ... end ``` An `else` branch matches any error that no preceding branch matches. ```mesa do account.transfer(amount, to: other) rescue case LedgerError.InsufficientFunds then ... else ... end ``` #### Propagation Unlike `when...case`, the branches of a `rescue` are *not* checked for exhaustiveness; they need only name the types they explicitly handle. An unhandled error propagates through calls until a `rescue` handles it, or until the program exits. ```mesa type DrawError case OutOfBounds(shape) end case InvalidDimensions(shape) end end do raise DrawError.OutOfBounds(shape) rescue case DrawError.InvalidDimensions then ... end ``` #### Binding If provided a name, `rescue` binds a variable to the raised error. This is unlike `when...case`, which has no binding form. ```mesa do account.transfer(amount, to: other) rescue err case LedgerError.InsufficientFunds then err.account # => Account(...) case LedgerError.InvalidRecipient then err.id # => ID(...) end ``` ### Hierarchies Mesa does not support subtyping. Instead, an error hierarchy is constructed by composing case types. ```mesa type DrawError case GeometryError(cause) end end type GeometryError case OutOfBounds(shape) end case InvalidDimensions(shape) end end ``` To handle the composed error variant, `rescue` matches against the outer type while `when...case` matches against the inner type. Whereas `rescue` *is not* checked for exhaustiveness, `when...case` *is*. ```mesa do window.draw(shape) rescue err case DrawError.GeometryError then when err.cause case GeometryError.OutOfBounds then ... else ... end end ``` An example of a set of errors defined using this structure is the predefined set of errors, described in Appendix C. ## 12. Modules Every Mesa program is composed of **modules**. A module is declared in a single file which contains its name, imports, declarations, and top-level expressions. This file contains the entire contents of the module; it may never be reopened or extended. ```mesa module Draw origin := 0 type Shape ... end def draw(shape) ... end ``` A module's declarations are *order-independent*. ```mesa module Draw def draw(shape: Circle(radius: 5)) ... end type Circle(radius) ... end ``` A module may contain top-level expressions in addition to declarations. They are evaluated when the module is loaded. ```mesa module Draw unit := scale(1) def scale(n) ... end ``` Unlike declarations, top-level expressions are *order-dependent*. ```mesa module Draw double := unit * 2 # runtime error: name 'unit' is not defined unit := scale(1) ``` ### Children A module may have **children** which are declared in files within a directory that sits beside the module's own file and shares its name. Child modules use a dot `.` to indicate their relationship, akin to member access. A child module declares its name as the full dotted path. ```mesa # draw/screen.ms module Draw.Screen ``` A module's filename carries no requirements to the language; only the module's prefix and its position are checked. A module is conventionally declared in a file named for its leaf, lowercased with underscores between words. ```mesa # draw.ms module Draw # draw/printer.ms module Draw.Printer # draw/screen.ms module Screen # semantic error: module 'Screen' must be declared under 'Draw' ``` Module nesting is a fact about names rather than lookup. Parent and child modules do not implicitly share names. A child module must import anything it uses from its parent or from a sibling. A parent module must import anything it uses from its children. ```mesa # draw.ms module Draw type Shape ... end # draw/screen.ms module Draw.Screen import Draw.Shape ``` ### Imports A module may **import** members of other modules, including other modules themselves. An import names a member by a dotted path and binds its last segment to a name in the module's scope. There is no wildcard or renaming import form. ```mesa module Draw import Draw.Screen import Draw.Screen.Window Screen # => module Draw.Screen Window # => type Window ``` An import of a module binds the module itself, so it may be passed to a proc, stored, or otherwise used as a value. ```mesa import Draw.Screen import Draw.Printer def render(shape, target: Screen) target.draw(shape) end render(shape) render(shape, target: Printer) ``` #### Cycles Modules may not form an import cycle. A cycle happens when a module imports another module that imports it, directly or indirectly. An import cycle is an error at declaration time. ```mesa # draw/screen.ms module Draw.Screen import Draw.Printer # semantic error: import cycle: Draw.Printer → Draw.Screen → Draw.Printer # draw/printer.ms module Draw.Printer import Draw.Screen ``` ### Members A module's declarations, its top-level assignments, and its child modules are its **members**, in the same sense as a type's, and are accessed by the same member operator `.`. ```mesa # draw.ms module Draw origin := 0 type Shape ... end def draw(shape) ... end # draw/screen.ms module Draw.Screen import Draw Draw.origin # => 0 Draw.Shape # => type Shape &Draw.draw # => def draw(shape) ``` An imported module *is not* a member of the importing module. There is no implicit re-export. ```mesa # draw/screen.ms module Draw.Screen import Draw.Shape Shape # => type Shape # draw/printer.ms module Draw.Printer import Draw.Screen Screen.Shape # runtime error: module Draw.Screen has no such member 'Shape' ``` A module's members share a single namespace. Two members may not share a name. ```mesa # draw.ms module Draw type Screen ... end # draw/screen.ms module Draw.Screen # semantic error: duplicate member 'Screen' ``` A module's members are read-only from outside the module. To mutate a member of another module, go through a proc call. ```mesa # draw.ms module Draw origin := 0 # draw/screen.ms module Draw.Screen import Draw Draw.origin := origin # runtime error: member 'origin' on module Draw is read-only ``` ## 13. Packages A **package** is a tree of modules. It is the unit of *compilation*, *evaluation*, and *distribution* of a Mesa program. When building an executable, it is evaluated as a package; when sharing a library, it is distributed as a package. A package has a **root module**, which is declared in `src/package.ms`. Every other module in the package descends from the root. `src/` is a build root rather than a module; it has no name in the language. The root module is the one module whose declaring file sits inside the directory holding its children rather than beside it. ``` tracker/ package.toml src/ package.ms # module Tracker groups.ms # module Tracker.Groups tags.ms # module Tracker.Tags tags/ label.ms # module Tracker.Tags.Label ``` A directory within `src/` must have a declaring file beside it. Like all other modules, children of the root module must have a prefix that matches their parent. ```mesa # src/package.ms module Tracker # src/groups.ms module Groups # semantic error: module 'Groups' must be declared under 'Tracker' ``` ### Exports Within a single package there is *no access control* mechanism. Just as no type hides its members, every module and its members are reachable from every other module. Between packages, **exports** declare which names are reachable. A module exports all of its members by default. An export list at the head of a module narrows this to just the members it names. ```mesa # src/package.ms module Tracker export Task, complete type Task ... end def complete(task) ... end ``` Every module has its own export list, and the lists *compose*. A name is reachable from outside a package when every member on its path is exported, from the root module's own members down to the member itself. A module's children are among its members, so an export list may name them. A module exports a child without importing it. ```mesa # src/package.ms module Tracker export Tags # src/tags.ms module Tracker.Tags export tag def tag(task) ... end # in another package import Tracker.Tags.tag ``` Where any module on the path does not export the next, the name is unreachable, whatever the modules below it export. ```mesa # src/package.ms module Tracker export Task type Task ... end # src/tags.ms module Tracker.Tags export tag def tag(task) ... end # in another package import Tracker.Tags.tag # semantic error: module 'Tracker' does not export 'Tags' ``` Exports govern the reachability of *names*. They never govern what may be seen of a value which is already named. An export list may only name *module members*, not members of the module's types themselves; an importer holding an exported value has access to the whole of it. ```mesa export Task.status # syntax error: unexpected token DOT ``` ### Manifest A package's **manifest** declares the package's name and its dependencies. The manifest file is `package.toml` in the package's root directory beside `src/`. ```toml name = "tracker" [dependencies] ledger = "https://github.com/acme/ledger" ``` A package's name identifies the package for distribution. It is never written in Mesa source. A package's root module name is declared in `src/package.ms`, like every other module's, and is the name source code uses. The two are conventionally related but the correspondence is not checked. ### Dependencies A package may **depend** on other packages, which it declares in its manifest by package name. Every module in a package may import any of the package's dependencies. Packages may not form a dependency cycle. A package may not depend on itself, directly or indirectly. ```toml # tracker/package.toml name = "tracker" [dependencies] ledger = "https://github.com/acme/ledger" # semantic error: dependency cycle: ledger → tracker → ledger # ledger/package.toml name = "ledger" [dependencies] tracker = "https://github.com/acme/tracker" ``` An import names a dependency by its root module name, which the dependency declares in its own `src/package.ms`, not by the package name under which the manifest declares it. ```mesa # src/tags.ms module Tracker.Tags import Ledger.Account ``` An import does not distinguish a dependency's module from a module of the importing package's own tree. ```mesa # src/tags.ms module Tracker.Tags import Ledger.Account import Tracker.Groups ``` Because imports do not distinguish them and there is no renaming import form, the root module names of a package and its dependencies share one space. A package depending on another package whose root module shares a name with its own, or on two packages whose root modules share a name with each other, is an error. ```toml [dependencies] ledger = "https://github.com/acme/ledger" account-book = "https://github.com/acme/account-book" # semantic error: packages 'ledger' and 'account-book' both declare root module 'Ledger' ``` Only a dependency's exported names are reachable. ```mesa import Ledger.Table # semantic error: module 'Ledger' does not export 'Table' ``` The **core library** is a dependency of every package and is not declared. Its root module is `Core`. This module shares the one namespace with every other dependency's root, so no package may declare a root module named `Core`. ```mesa import Core.Behaviors.Order ``` ## Appendix A - Grammar ``` program ::= { module-item } module-item ::= module-decl | import-decl | export-decl | type-decl | extern-decl | proto-decl | proc-decl | expr module-decl ::= "module" dotted-path import-decl ::= "import" dotted-path dotted-path ::= IDENT { "." IDENT } export-decl ::= "export" IDENT { "," IDENT } type-decl ::= "type" IDENT [ "(" params ")" ] [ impl-clause ] { case-decl } { field-decl } { type-decl | method-decl } "end" case-decl ::= "case" IDENT [ "(" params ")" ] [ impl-clause ] { field-decl } { type-decl | method-decl } "end" field-decl ::= IDENT ":=" expr method-decl ::= "def" [ "self" "." ] IDENT [ "(" params ")" ] { expr } "end" extern-decl ::= "extern" "type" IDENT [ impl-clause ] { method-decl } "end" proto-decl ::= "proto" IDENT { proc-decl } "end" impl-clause ::= "impl" dotted-path { "," dotted-path } proc-decl ::= "def" IDENT [ "(" params ")" ] { expr } "end" params ::= [ param { "," param } ] param ::= IDENT [ ":" expr ] expr ::= each-expr | loop-expr | when-expr | do-expr | proc-expr | return-expr | break-expr | raise-expr | assign-expr | binary-expr | unary-expr | mention-expr | call-expr | member-expr | access-expr | primary each-expr ::= "each" IDENT "in" expr "do" { expr } "end" loop-expr ::= "loop" "do" { expr } "end" when-expr ::= "when" expr ( "then" { expr } ( "end" | else-clause ) | case-arms ) else-clause ::= "else" ( when-expr | { expr } "end" ) case-arms ::= { case-arm } [ "else" { expr } ] "end" case-arm ::= "case" dotted-path "then" { expr } do-expr ::= "do" { expr } ( "end" | "rescue" [ IDENT ] case-arms ) proc-expr ::= "def" [ "(" params ")" ] { expr } "end" return-expr ::= "return" [ expr ] break-expr ::= "break" [ expr ] raise-expr ::= "raise" expr assign-expr ::= place ":=" expr place ::= IDENT | member-expr | access-expr binary-expr ::= expr bin-op expr bin-op ::= "or" | "and" | "==" | "!=" | "<" | ">" | "<=" | ">=" | "<<" | "+" | "-" | "*" | "/" unary-expr ::= ( "not" | "-" ) expr mention-expr ::= "&" expr call-expr ::= expr "(" args ")" args ::= [ arg { "," arg } ] arg ::= [ IDENT ":" ] expr member-expr ::= expr "." IDENT access-expr ::= expr "[" expr "]" primary ::= "self" | IDENT | BUILTIN "(" expr ")" | STR | CHAR | NUM | BOOL | NIL | "[" [ expr { "," expr } ] "]" | "{" [ expr ":" expr { "," expr ":" expr } ] "}" | "(" expr ")" IDENT ::= ( letter | "_" ) { letter | digit | "_" } [ "?" | "!" ] NUM ::= digit { digit } [ "." digit { digit } ] STR ::= '"' { any | escape } '"' CHAR ::= "'" ( any | escape ) "'" escape ::= "\n" | "\t" | "\"" | "\'" | "\\" BOOL ::= "true" | "false" NIL ::= "nil" BUILTIN ::= "$" IDENT COMMENT ::= "#" { any except "\n" } ``` ## Appendix B - Built-in Behaviors Some protocols are provided which offer a limited mechanism for user types to hook into built-in behaviors, including some operators. The protocols and the behaviors they govern are as follows: | Protocol | Governs | Status | |-----------|---------------------------|------------------------| | `Equal` | `== !=` | Automatic, overridable | | `Hash` | Use as a `Dict` key | Opt-in | | `Order` | `< <= > >=` | Opt-in | | `Access` | `a[k]` and `a[k] := v` | Opt-in | | `Append` | `<<` | Opt-in | | `Iterate` | Use in `each`1 | Opt-in | | `Display` | User-facing output | Opt-in | | `Inspect` | Developer-facing output | Automatic, overridable | 1. `Iterate` pairs with the `Advance` protocol, detailed below. These protocols are all defined in the `Core.Behaviors` module of the core library. ### `Equal` `Equal` indicates that values of a type can be tested against one another for (in)equality. `equal` should return `true` when `self` is equivalent to `other` and `false` otherwise. By implementing `Equal`, values of a type can be used with the equality operators `== !=`. `Equal` is implemented by default for all types, both built-in and user-defined. It is *structural* over all fields, including body fields. For a value of a given type, another value of that same type is equal to the first if all the values of its fields are equal. For a value of a variant, another value of that same variant is equal to the first both if the fields are equal and if the values are of the same variant. ```mesa "Hello, world!" == "Hello, world!" # => true type Task(priority) end type Group(tasks: []) end Task(5) == Task(5) # => true Task(5) == Task(2) # => false Group([Task(2)]) == Group([Task(2)]) # => true ``` Equality is guaranteed to terminate in the case of referential cycles. Structural equality is intended to determine *semantic equivalence* rather than referential identity. Two graphs of cyclic values are equal if every path traversal through their fields yields the same sequence of values. ```mesa type Node(data) end a := Node(nil) a.data := a b := Node(nil) c := Node(b) b.data := c a == b # => true ``` `Equal` is implemented for all user-defined types, so `impl Equal` on any type is redundant. Consequently, any type implementing an `equal` method will override the behavior of `==`. ```mesa type Account(id, name) def equal(other) id == other.id end end Account(1, "Checking") == Account(1, "Savings") # => true ``` ### `Hash` `Hash` indicates that values of a type can deterministically provide a representation of their identity. `hash` should return a `Digest` that deterministically identifies the value. By implementing `Hash`, values of a type can be used as a key in a `Dict`. `Hash` is implemented for `Num`, `Bool`, `Char`, `Str`, `Digest`, `Proc`, `Type`, `Module`, and `Nil`. `List` and `Dict` do *not* implement `Hash`. `Hash` requires that `Equal` be implemented. Two values which are equal should hash to the same value. A mutable value with an unstable identity *should not* implement `Hash`, because a mutation after its insertion into a `Dict` would cause future lookups to hash to a different value. ```mesa import Core.Behaviors.Hash type Account(id, name, transactions) impl Hash def equal(other) id == other.id and name == other.name end def hash id.hash + self.name.hash end end ``` A type which implements `Hash` and overrides `equal` must also override `hash`. Not doing so is an error at declaration time. ### `Order` `Order` indicates that values of a type can be compared against one another to produce a coherent ordering. `order` should return `-1` when `self < other`, `1` when `self > other`, and `0` otherwise. By implementing `Order`, values of a type can be used with the comparison operators `< <= > >=`. `Order` is implemented for `Num`, `Str`, and `Char`. `Num` sorts by numeric value. `Str` and `Char` sort by *scalar value*, not locale collation. ```mesa import Core.Behaviors.Order type Task(priority) impl Order def order(other) priority.order(other.priority) end end Task(0) < Task(1) # => true Task(1) >= Task(0) # => true Task(3) < Task(1) # => false Task(2) <= Task(2) # => true ``` The ordering produced by a type implementing `Order` must be *total*, *transitive*, and *reflexive*. It need only be antisymmetric with respect to the sorted equivalence of the values. `order` returning `0` operates independently of structural equality. For example, a user-defined type might implement an ordering in terms of a single field, resulting in two structurally distinct objects evaluating as equivalent under `Order`. The ordering produced by a type implementing `Order` can be thought of as one over a *projection* of the values of the type rather than the values themselves. ```mesa import Core.Behaviors.Order type Task(name, priority) impl Order def order(other) priority.order(other.priority) end end Task("Documentation", 1).order(Task("Refactor", 1)) # => 0 Task("Documentation", 1) == Task("Refactor", 1) # => false ``` ### `Access` `Access` indicates that values of a type can have their elements read from or written to using access syntax. `access` should return the item corresponding to the given key. `store` should set the given key to the given item. Implementing `access` is required, but implementing `store` is optional. Implementing `access` without implementing `store` indicates that values of the type cannot be mutated using access syntax. In this case, an error will be raised when calling `store`. `Access` is implemented for `List` and `Dict`. Accessing a list by an out-of-bounds index will raise an error. Accessing a dict by a key which does not implement `Hash` will raise an error. ```mesa import Core.Behaviors.Access type Collection(items: []) impl Access def access(key) items[key] end def store(key, item) items[key] := item end end collection := Collection(["Item 1", "Item 2"]) collection[0] # => "Item 1" collection[1] := "Item 3" # => "Item 3" ``` ### `Append` `Append` indicates that values of a type can be appended to. `append` should append the given item and return `self` to support chaining. By implementing `Append`, values of a type can be used with the `<<` operator. `Append` is implemented for `List`, and appends the item to the list. ```mesa import Core.Behaviors.Append type Collection(items: []) impl Append def append(item) items << item self end end Collection() << "Item" # => Collection(items: ["Item"]) ``` ### `Iterate` `Iterate` indicates that values of a type can be iterated using the built-in `each` construct. `iterate` should return an `Advance` which yields the successive values until it is exhausted. `Iterate` is implemented for `List`, `Dict`, and `Str`. `List` yields items, `Dict` yields keys, and `Str` yields characters. ```mesa import Core.Behaviors.Iterate type Group(tasks: []) impl Iterate def iterate tasks.iterate end end group := Group([Task(...)]) each task in group do ... end ``` ### `Advance` `Advance` indicates that values of a type represent an exhaustable iterator. `advance` should advance the iterator and return `true` if the iterator has more items, or `false` otherwise. `item` should return the current item, or `nil` if the iterator is exhausted. `each` calls `advance` then `item` repeatedly until `advance` returns `false`. ```mesa import Core.Behaviors.Advance import Core.Behaviors.Iterate type LinkedList impl Iterate head := nil type Node(data, next) end type Iter(node) impl Advance started? := false def advance when not started? then started? := true else when node != nil then node := node.next end node != nil end def item when node != nil then node.data end end end def iterate LinkedList.Iter(head) end end ``` ### `Display` `Display` indicates that values of a type can be converted to user-facing output. `display` should return a `Str` which is suitable to display to a user. `Display` is implemented for `Str` and `Char`, and returns the contents of the respective string or character without quotes and with escape sequences translated to their literal equivalents. `Display` requires that `Inspect` be implemented. A value which cannot be printed in a user-facing format prints using a developer-facing format. No value is unprintable. ```mesa "\"Hello\nworld!\"".display # => "\"Hello\nworld!\"" ``` ### `Inspect` `Inspect` indicates that values of a type can be inspected by a developer for runtime debugging. `inspect` should return a `Str` which provides information about the value. In general, `inspect` should return something which would be valid syntax for creating that value, but this is not a requirement. `Inspect` is implemented for all types, both built-in and user-defined. `Num` returns the number's decimal representation using the least amount of precision necessary. `Bool` returns `"true"` or `"false"`. `Nil` returns `"nil"`. `Char` returns the single-quoted character, escapes included. `Str` returns the double-quoted string, escapes included. `List` returns the comma-separated items surrounded by square brackets. `Dict` returns the comma separated key-value pairs separated by curly braces. `Inspect` is implemented for all user-defined types and returns a constructor invocation including both constructor and body fields and their values. ```mesa type Account(name) transactions := [] end Account("Checking").inspect # => "Account(name: \"Checking\", transactions: [])" ``` `Inspect` is implemented for all user-defined types, so `impl Inspect` on any type is redundant. ## Appendix C - Built-in Errors | Case Type | Variant | Description | |---------------|------------------|------------------------------------------------------------------------| | Error | ProtocolError | A value did not conform to a particular protocol. | | Error | ArgumentError | The arguments provided to a proc or constructor were invalid. | | Error | TypeError | A value of an incorrect type was used. | | Error | MemberError | A member was incorrectly accessed. | | Error | IndexError | An invalid `List` index was provided. | | Error | NameError | An undefined name was read. | | Error | KeyError | An invalid `Dict` key was provided. | | ProtocolError | NotIterable | A value that does not implement `Iterate` was used with `each`. | | ProtocolError | NotAccessible | A value that does not implement `Access` was used with `[]`. | | ProtocolError | NotAppendable | A value that does not implement `Append` was used with `<<`. | | ProtocolError | NotOrderable | A value that does not implement `Order` was used with `< <= > >=`. | | ProtocolError | NotImplemented | A method was called on a type that only implements part of a protocol. | | ArgumentError | Missing | A required argument was not provided. | | ArgumentError | TooMany | Too many positional arguments were provided. | | ArgumentError | Unknown | An unknown keyword argument was provided. | | ArgumentError | Duplicate | A duplicate keyword argument was provided. | | TypeError | IndexNonNum | A `List` index that is not a `Num` was provided. | | TypeError | ArithNonNum | A value that is not a `Num` was used with `+ - * /`. | | TypeError | ConcatNonStr | A value that is not a `Str` was used with `+`. | | TypeError | NotCallable | A value that is not callable was used with `()`. | | TypeError | NotConstructible | A case type, rather than a variant, was constructed. | | TypeError | NotInvocable | A value that is not invocable was used with `&`. | | TypeError | CaseNonType | A value that is not a type was used in a `when...case` branch. | | MemberError | Missing | A member was read from that does not exist. | | MemberError | ReadOnly | A member was written to that is read-only. | | IndexError | OutOfRange | A `List` index that is outside the bounds of the list was provided. | | IndexError | NonIntegral | A `List` index that is a non-integral `Num` was provided. | ```mesa type Error case ProtocolError(cause) end case ArgumentError(cause) end case TypeError(cause) end case MemberError(cause) end case IndexError(cause) end case NameError(message) end case KeyError(message) end end type ProtocolError case NotIterable(message) end case NotAccessible(message) end case NotAppendable(message) end case NotOrderable(message) end case NotImplemented(message) end end type ArgumentError case Missing(message) end case TooMany(message) end case Unknown(message) end case Duplicate(message) end end type TypeError case IndexNonNum(message) end case ArithNonNum(message) end case ConcatNonStr(message) end case NotCallable(message) end case NotConstructible(message) end case NotInvocable(message) end case CaseNonType(message) end end type MemberError case Missing(message) end case ReadOnly(message) end end type IndexError case OutOfRange(message) end case NonIntegral(message) end end ``` ## Appendix D - Operator Precedence | Operator | Description | Form | Associativity | |-----------------|--------------------------|----------------------|---------------| | () [] . | Call, access, member | Postfix | Left-to-right | | & | Invocable mention | Prefix | Right-to-left | | - | Arithmetic negation | Prefix | Right-to-left | | * / | Multiplication, division | Infix | Left-to-right | | + - | Addition, subtraction | Infix | Left-to-right | | << | Append | Infix | Left-to-right | | == != < <= > >= | Comparison | Infix | Left-to-right | | not | Logical negation | Prefix | right-to-left | | or and | Logical or, and | Infix | Left-to-right | | := | Assignment | Infix, place on left | Right-to-left |