Lunar Programming Language

by David A. Moon
January 2017 - January 2018



Definitions

A definition is a statement that defines a meaning for a name, or for multiple names when destructuring is used.

Scope

The scope of a definition is the range of code within which that definition is visible. We say a definition is in scope in that code. The meaning of a name in an expression is determined by the definition of that name that is in scope where the name appears.

A scope datum is a compile-time object that maps names to definitions.

Scopes nest: When looking up a name in a scope, if the scope has no mapping for that name, lookup tries the parent scope.

A method can reference definitions in any scope enclosing the method. Thus all methods are closures in the Lisp sense of closure.

Each scope is either local or global. A local scope is a specific range of code, such as the body of a function. A global scope is an unlimited range of code; more code can always be added. A global scope cannot be nested in another scope, but see Modules for a description of module imports, which are similar to scope nesting.

The scope of a definition in a global scope is all code in that scope, without regard to its order of appearance. However, it is an error to use a definition that affects parsing, such as an operator definition, before it is defined.

The scope of a definition in a local scope is the rest of the block. Thus a local definition is visible only in code inside the same block after the definition, except where it is hidden by another definition with the same name in a nested scope.

If the name being defined is a name_in_context whose context is a module, the definition statement must be in the global scope and makes a definition in the specified module. Otherwise, a definition statement in the global scope defines a global definition in the current module, while a definition statement in a local scope defines a local definition in that scope. Hygienic context might prevent a global or local definition from being visible.

Local defmacro and defoperator work because a known definition is recorded immediately before parsing the rest of the block. Local defmodule and defclass are permitted but are probably useless.

The scope of a formal parameter is the rest of the formal parameter list along with the body of the method.

The scope of a compound definition such as a class or method definition includes the definition itself. This makes it easy to define recursive types and functions. A compound definition first does a forward definition of the class or function name as a bundle unless it is already defined directly in the current scope, not inherited from the parent scope. Then a compound definition adds function or class nature to that bundle. The definition is in scope in the nature-adding code.

A class definition also does a forward definition of the constructor if its name is not the same as the class name, and similarly any necessary forward definitions for slot reader_names and writer_names. Slot accessor methods defined for the default . function never do a forward definition of . so they always use the . that is already visible, usually the definition in the lunar module.

When defining multiple methods for the same function bundle in a local scope, no other definitions can intervene between the method definitions, to ensure that all method definitions are in the same scope and add methods to the same bundle. This is not an issue when defining methods in a global scope.

When defining mutually recursive functions or classes in a local scope, explicit forward declarations must be used to extend the scope of definitions after the first to include the first definition. This is not an issue for mutually recursive definitions in a global scope.

Hoisting

When a definition is inside an expression, rather than directly in a block or at top level, it is hoisted in front of its containing expression. For example,

if def x = g(y) then x + 1 else 99
is equivalent to
def x = g(y)
if x then x + 1 else 99
The definition of x is in scope inside the if statement and also after the if statement.

This also works for the left-hand side of and and or, thus

(def x = g(y)) or 99
is equivalent to
def x = g(y)
x or 99
and the definition of x is in scope for the rest of the containing block, or in the entirety of a global scope.

On the right-hand side,

g(y) or def x = 99
is equivalent to
g(y) or block
          def x = 99
and the definition of x is not visible outside the or.

The end test in a while or until statement is not hoisted, because the entire statement is enclosed in an implicit block.

Hoisting also works for function calls, whether written with parentheses or with operator syntax. For example,

f(def x = g(y), x + 1)
is equivalent to
def x = g(y)
f(x, x + 1)
and
1 + def x = g(y)
is equivalent to
def x = g(y)
1 + x

We need to be careful to preserve order of operations and the scope of names to the left of the hoisted definition. Thus

f(x, def x = g(y), x + 1)
is equivalent to
def temp = x
def x = g(y)
f(temp, x, x + 1)
and
f(def f = g, f(x))
is equivalent to
def temp = f
def f = g
temp(f, f(x))
where temp is a name that is not visible anywhere else.

Constant Definition

The syntax of a constant definition is

$def destructuring "=" value_expression
which evaluates the value_expression and assigns the result to the destructuring. See Destructuring for an explanation of possibilities besides a simple name for the destructuring syntax. In the absence of destructuring, this expands into a known_definition.

Variable Definition

The syntax of a variable definition is

$def name ":=" value_expression [ type_expression ]
which evaluates the value_expression and assigns the result to the name. This expands into a variable_definition.

If the optional type_expression is present, it is evaluated and its result restricts the type of future assignments to the left-hand side.

Destructuring is not allowed in variable definitions and assignment statements because of syntactic ambiguity.

Method Definition

A method definition implicitly defines a name to be a function, and adds a method to that function. Alternatively it can add a method to an operator that has previously been defined with defoperator.

The syntax of an ordinary method definition is

$def methodhead body
This expands into a forward declaration of the function name if the current scope is a local scope and the name is not already defined directly in that scope, followed by
add_method(function_name, method_expression)

The syntax of a generic method definition is similar but with a list of generic formal parameter names prepended

$def "[" {generic_name & ","}+ "]" methodhead body

A method head is everything about a method except the body, i.e. the name, modifiers, formal parameters, and result type. The syntax of a methodhead is

( { name & "." }+ [ "[" generic_class_formalparameters "]" ]
                  "(" methodmodifiers formalparameters ")" |
  operator "(" name type_expression ")" |
  "(" name1 type_expression1 ")" operator ( "(" name2 type_expression2 ")" |
                                            "#" constant ) |
  "(" name1 type_expression1 ")" infix_macro rhs
) [ ":=" "(" new_value_name new_value_type_expression ")" ]
  [ "=>" result_type_expression ]

When a method head begins with multiple names separated by . the method is defined for a name imported from another module. The first name is in the local scope.

If generic_class_formalparameters are present, in square brackets, between the name and the formal parameters, the method is added to a function bundle that is also a generic class bundle. Each instantiation of the generic class gets its own method with specific values of the generic_class_formalparameters. This is mainly useful for defining pseudo-constructors for generic classes.

Note that when a method head contains an operator the formal parameters before and after the operator must have types specified and must be enclosed in parentheses. For symmetry, the same is true of the new_value_expression and new_value_type_expression after :=.

If "(" name2 type_expression2 ")" is replaced by "#" constant it is an abbreviation for an anonymous formal parameter whose type is set(#constant), i.e. it only accepts constant as an actual parameter.

In the infix_macro method syntax, the macro expander will be called with its method_head? parameter true and name1 as its lhs parameter. The expansion of the macro can be a call_expression or a method_head.

When := is present in a method head, ":=" is appended to the function name and , new_value_expression new_value_type_expression is appended to the formal parameters, becoming a method definition for an assignment function. This is the same transformation done by the := macro.

Forward Definition

The syntax of a forward definition is simply

$def name
This expands into a known_definition of the name as an empty bundle. Presumably method and/or class definition(s) of the same name will appear later in the scope of this definition and add function and/or class nature to the bundle.

It is not meaningful to do a forward definition of an operator.

See Parsing the Def Statement for how the def statement can be parsed.

Operator Definition

The syntax of an operator definition is

$defoperator anyname
    "precedence:" { precedence_integer & "," }+
  [ "arity:"      { arity_name & "," }+ ]
  [ "macro:"      lhs_name pattern "=>" body ]
anyname is the name of the operator. This expands into a known_definition of anyname as a bundle with function and operator natures.

precedence: must be followed by one integer (equal left and right precedence) or two integers left_precedence,right_precedence. It is required.

arity: must be followed by a valid set of arity names. It defaults to infix if macro: is present, otherwise to binary. Defining a prefix macro with the same name will add prefix to the arity set.

Macro Definition

The syntax of a prefix macro definition is

$defmacro anyname pattern "=>" body

See Macros for details.

An infix macro is defined with defoperator. See Operator Definition

Class Definition

The syntax of a class definition is

$defclass class_name
          [ "[" generic_formalparameters "]" ]
          [ "constructor:" constructor_name ]
          [ "(" constructor_formalparameters ")" ]
          { superclass_expression & "," }*
          [ ^ "disjoint:" { disjoint_class_expression & "," }+ ]
          { ^ slot_name [ "[" length_expression "]" ] ( "=" | ":=" )
              initial_value_expression [ type_expression ]
              [ "reader:" reader_name ]
              [ "writer:" writer_name ] }*

This expands into a known_definition of the class_name as a bundle followed by code to add class nature, and to add function nature if a different constructor_name was not specified.

Unless suppressed by an abstract: or singleton: class modifier, it also expands into a method definition of the constructor_name or class_name with constructor_formalparameters and a suitable body to create and initialize an instance of the class.

It also expands into a sealed method definition for each slot reader or writer.

If generic_formalparameters are supplied, this defines a generic class.

Each superclass_expression specifies one superclass. The order of superclasses is immaterial. The expression should be the superclass name (not its constructor_name) optionally followed by generic actual parameters in brackets, optionally followed by constructor actual parameters in parentheses. If no brackets are specified, there are no generic actual parameters. If no parentheses are specified, the constructor actual parameter list is empty, the same as if () had been specified.

The generic_formalparameters and constructor_formalparameters are in scope while executing the part of a superclass_expression after the superclass name.

The optional disjoint: line specifies one or more classes with which this class is disjoint, which means they can never have any members in common. This relationship is symmetric and is inherited by subclasses. It can be queried by the disjoint? function.

After the first line of a class definition and the optional disjoint: line, each remaining line defines a slot. Each slot must be on a separate line.

By convention a slot or constructor that should be considered private has a name that starts with underscore. Lunar does not define which methods if any are considered to be within the private domain and thus have the right to access private slots; that is up to the programmer. Thus privateness is enforced by social disapprobation, not by the compiler.

A slot is a constant slot if = is specified or a variable slot if := is specified. Its initial value is the result of initial_value_expression. If type_expression is present, its result restricts the type of values assigned to the slot.

Note that a slot cannot be uninitialized.

A slot is a multi-slot if a length_expression is specified. In this case the result of the length_expression is the length of the slot and the result of initial_value_expression is a sequence of initial values. That sequence must have at least as many members as the length of the slot. The type_expression is the type of the individual values. The result of reading a multi-slot is a succession of that type whose keys are consecutive integers starting at zero, similar to a list. Only the last slot can be a multi-slot.

The generic_formalparameters and constructor_formalparameters are in scope while executing a slot's length_expression, initial_value_expression, and type_expression.

If a slot has a reader_name the reader method for the slot is

def reader_name(instance class_name) ...
otherwise it is
def (instance class_name).slot_name ...

If a variable slot has a writer_name the writer method for the slot is

def writer_name(instance class_name, new_value slot_type) ...
otherwise it is
def (instance class_name).slot_name := (new_value slot_type) ...
However, a multi-slot never has a writer method. The reader method for a variable multi-slot returns a mutable succession.

If a variable slot has a reader_name but no writer_name specified, its writer_name defaults to reader_name concatenated with ":=" with the same hygienic context.

Simple Class Definition

A "simple class definition" omits the slot declarations. It implicitly declares a slot for each name in the formal parameter list of the constructor that is not used anywhere in the superclass constructor actual parameters. The slot has the same name and type as the formal parameter. The slot is initialized to the corresponding actual parameter. The slots are variable slots unless the constant: class modifier is used. The slots are never multi-slots.

If the constructor's formal parameter list uses destructuring, there is a separate slot for each name inside each destructuring.

A "simple class definition" defines a destructuring method for the constructor_name or class_name. See Destructuring for details.

For example:

defclass point(x, y, optional: z)
defines a class named point with three slots.

Class Modifiers

The following modifiers can precede a class definition:

abstract: Do not define a constructor
export: Export class name and constructor name (unless it starts with _) and reader and writer names (unless they start with _) from current module
constant: All slots are constant in simple class definition
intrinsic: Specially known to the compiler
sealed: Any subclasses must be defined in the same source file
singleton: Class is its own sole instance; implies sealed and no constructor
autothrow: Constructor throws the new instance
noreturn: Constructor throws an error if autothrow does not take an early exit


Previous page   Table of Contents   Next page



Creative Commons License
Lunar by David A. Moon is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Please inform me if you find this useful, or use any of the ideas embedded in it.
Comments and criticisms to dave underscore moon atsign alum dot mit dot edu.