# Advanced concepts

This chapter covers advanced concepts of the writing system. The first subject is the exception model. The second subject covers some properties of the namesets. Finally, the interpreter object is described in details.

## Exception

An exception is an unexpected change in the execution flow. The exception model is based on a mechanism which throws the exception to be caught by a handler. The mechanism is also designed to be compatible with the native "C++" implementation.

## Throwing an exception

An exception is thrown with the reserved keyword `throw`. When an exception is thrown, the normal flow of execution is interrupted and an object used to carry the exception information is created. Such exception object is propagated backward in the call stack until an exception handler catch it.

if (not (number-p n)) (throw "type-error" "invalid object found" n)

The example above is the general form to throw an exception. The first argument is the the exception id. The second argument is the exception reason. The third argument is the exception object. The exception id and reason are always a string. The exception object can be any object which is carried by the exception. The reserved keyword `throw` accepts 0 or more arguments.

throw throw "type-error" throw "type-error" "invalid argument"

With 0 argument, the exception is thrown with the exception id set to "user-exception". With one argument, the argument is the exception id. With 2 arguments, the exception id and reason are set. Within a try block, an exception can be thrown again by using the exception object represented with the `what` symbol.

try { ... } { println "exception caught and re-thrown" throw what }

## Exception handler

The special form `try` executes a form and catch an exception if one has been thrown. With one argument, the form is executed and the result is the result of the form execution unless an exception is caught. If an exception is caught, the result is the exception object. If the exception is a native one, the result is nil.

try (+ 1 2) try (throw) try (throw "hello") try (throw "hello" "world") try (throw "hello" "world" "folks")

In its second form, the `try` reserved keyword can accept a second form which is executed when an exception is caught. When an exception is caught, a new nameset is created and the special symbol `what` is bounded with the exception object. In such environment, the exception can be evaluated.

## Symbol bound to the exception object

Symbol | Description
--- | ---
eid | Exception id
name | Exception file name
line | Exception line number
about | Exception extended reason
reason | Exception reason
object | Exception object

try (throw "hello") (eval what:eid) try (throw "hello" "world") (eval what:reason) try (throw "hello" "world" 2000) (eval what:object)

Exceptions are useful to notify abruptly that something went wrong. With an untyped language, it is also a convenient mechanism to abort an expression call if some arguments do not match the expected types.

# protected factorial const fact (n) { if (not (integer-p n)) (throw "number-error" "invalid argument in fact") if (== n 0) 1 (* n (fact (- n 1))) } try (fact 5) 0 try (fact "hello") 0

## Nameset

A nameset is created with the reserved keyword `nameset`. Without argument, the `nameset` reserved keyword creates a nameset without setting its parent. With one argument, a nameset is created and the parent set with the argument.

const nset (nameset) const nset (nameset ...)

## Default namesets

When a nameset is created, the symbol `.` is automatically created and bound to the newly created nameset. If a parent nameset exists, the symbol `..` is also automatically created. The use of the current nameset is a useful notation to resolve a particular name given a hierarchy of namesets.

trans a 1 # 1 block { trans a (+ a 1) # 2 println ..:a 1 # 1 } println a # 1

## Nameset and inheritance

When a nameset is set as the super object of an instance, some interesting results are obtained. Because symbols are resolved in the nameset hierarchy, there is no limitation to use a nameset to simulate a kind of multiple inheritance. The following example illustrates this point.

const cls (class) const ins (cls) const ins:super (nameset) const ins:super:value 2000 const ins:super:hello "hello world " println ins:hello ins:value # hello world 2000

## Delayed Evaluation

The engine provides a mechanism called delayed evaluation. Such mechanism permits the encapsulation of a form to be evaluated inside an object called a promise.

## Creating a promise

The reserved keyword `delay` creates a promise. When the promise is created, the associated object is not evaluated. This means that the promise evaluates to itself.

const a (delay (+ 1 2)) promise-p a # true

The previous example creates a promise and store the argument form. The form is not yet evaluated. As a consequence, the symbol `a` evaluates to the promise object.

## Forcing a promise

The reserved keyword `force` the evaluation of a promise. Once the promise has been forced, any further call will produce the same result. Note also that, at this stage, the promise evaluates to the evaluated form.

trans y 3 const l ((lambda (x) (+ x y)) 1) assert 4 (force l) trans y 0 assert 4 (force l)

## Enumeration

Enumeration, that is, named constant bound to an object, can be declared with the reserved keyword `enum`. The enumeration is built with a list of literal and evaluated as is.

const e (enum E1 E2 E3) assert true (enum-p e)

The complete enumeration evaluates to an `Enum` object. Once built, enumeration item evaluates by literal and returns an `Item` object.

assert true (item-p e:E1) assert "Item" (e:E1:repr)

Items are comparable objects. Only items can be compared. For a given item, the source enumeration can be obtained with the `get-enum` method.

# check for item equality const i1 e:E1 const i2 e:E2 assert true (i1:== i1) assert false (== i1 i2) # get back the enumeration assert true (enum-p (i1:get-enum))

## Logger

The Looger class is a message logger that stores messages in a buffer with a level. The default level is the level 0. A negative level generally indicates a warning or an error message but this is just a convention which is not enforced by the class. A high level generally indicates a less important message. The messages are stored in a circular buffer. When the logger is full, a new message replace the oldest one. By default, the logger is initialized with a 256 messages capacity that can be re-sized.

const log (Logger) assert true (logger-p log)

When a message is added, the message is stored with a time-stamp and a level. The time-stamp is used later to format a message. The `length` method returns the number of logged messages. The `get-message` method returns a message by index. Because the system operates with a circular buffer, the `get-message` method manages the indexes in such way that the old messages are accessible with the oldest index. For example, even after a buffer circulation, the index 0 will point to the oldest message. The `get-message-level` returns the message level and the `get-message-time` returns the message posted time.

const mesg (log:get-message 0)

In term of usage, the logger facility can be conveniently used with other derived classes. The standard i/o module provides several classes that permits to manage logging operations in a convenient way.

## Interpreter

The interpreter is by itself a special object with specialized methods which do not have equivalent using the standard notation. The interpreter is always referred with the special symbol `interp`. The following table is a summary of the symbols and methods bound to the interpreter.

## Interpreter built-in symbols

Symbol | Description
--- | ---
argv | Command arguments vector
os-name | Operating system name
os-type | Operating system type
version | Full version
program-name | Interpreter program name
major-version | Major version number
minor-version | Minor version number
patch-version | Patch version number
afnix-uri | Official uri name
load | Load a file and execute it
launch | Launch a normal thread
library | Load and initialize a library
set-absolute-precision | Set absolute precision
set-relative-precision | Set relative precision
get-absolute-precision | Get absolute precision
get-relative-precision | Get relative precision

## Arguments vector

The `interp:argv` qualified name evaluates to a vector of strings. Each argument is stored in the vector during the interpreter initialization.

zsh> axi hello world (axi) println (interp:argv:length) # 2 (axi) println (interp:argv:get 0) # hello

## Interpreter version

Several symbols can be used to track the interpreter version and the operating system. The full version is bound to the `interp:version` qualified name. The full version is composed of the major, minor and patch number. The operating system name is bound to the qualified name `interp:os-name`. The operating system type is bound to the `interp:os-type`.

println "major version number : " interp:major-version println "minor version number : " interp:minor-version println "patch version number : " interp:patch-version println "interpreter version : " interp:version println "operating system name : " interp:os-name println "operating system type : " interp:os-type println "afnix official uri : " interp:afnix-uri

## File loading

The `interp:load` method loads and execute a file. The interpreter interactive command session is suspended during the execution of the file. In case of error or if an exception is raised, the file execution is terminated. The process used to load a file is governed by the file resolver. Without extension, a compiled file is searched first and if not found a source file is searched.

## Library loading

The `interp:library` method loads and initializes a library. The interpreter maintains a list of opened library. Multiple execution of this method for the same library does nothing. The method returns the library object.

interp:library "afnix-sys" println "random number: " (afnix:sys:get-random)

## Interpreter duplication

The interpreter can be duplicated with the help of the `dup` method. Without argument, a clone of the current interpreter is made and a terminal object is attached to it. When used in conjunction with the `roll` method, this approach permits to create an interactive interpreter. The `dup` method also accepts a terminal object.

# duplicate the interpreter const si (interp:dup) # change the primary prompt si:set-primary-prompt "(si)"

## Interpreter loop

The interpreter loop can be run with the `roll`. The loop operates by reading the interpreter input stream. If the interpreter has been cloned with the help of the `dup` method, this method provides a convenient way to operate in interactive mode. The method is not called loop because it is a reserved keyword and starting a loop is like having the ball rolling.

# duplicate the interpreter const si (interp:dup) # loop with this interpreter si:roll