Erda
1 ErdaRVM
1.1 Modules and Macros
1.2 Defining Forms
define
declare
1.2.1 Alerts
1.3 Expressions
#%datum
quote
value
if
if-not
or
and
cond
anti-do
try
default
on-alert
block
1.4 Standard Library
result?
good-result?
bad-result?
raise
2 ErdaRVMσ
if
when
unless
3 ErdaC+  +
define
declare
3.1 C+  +   Translation Advising Annotations
4 Example Code
5 License
6.1.1

Erda

Tero Hasu

ErdaRVM, ErdaRVMσ, and ErdaC++ constitute a family of small programming languages and implementations for experimenting with error handling mechanisms. We use the unqualified name Erda to refer to the language family as a whole, or any one member of the family where the languages are all alike in relevant respects.

The “concrete” syntax of Erda resembles that of Racket (and Scheme).

1 ErdaRVM

 #lang erda/rvm package: erda

The ErdaRVM language is a dynamically typed language that includes an alerts mechanism for declarative error reporting, and transparently propagates errors as data values.

This document describes the syntax and semantics of a selection of the ErdaRVM-specific constructs.

The erda/rvm language also inherits a number of constructs directly from Racket, including the begin, begin0, let, let*, letrec, require, and provide syntactic forms, and the not function. These forms should therefore behave as described in the Racket documentation. Note, however, that functions may seemingly behave differently due to ErdaRVM’s different function application semantics.

1.1 Modules and Macros

The Racket require and provide forms (and associated sub-forms) may be used in Erda as normal to import modules and to define the interfaces exposed by modules.

Macros are not included in the language by default, but there is nothing preventing from require’ing macro support from Racket.

1.2 Defining Forms

syntax

(define id expr)

(define (id arg ...) maybe-alerts expr ...+)
(define (id arg ...) #:handler maybe-alerts expr ...+)
(define (id arg ...) #:direct expr ...+)
Forms used to define variables and functions.

The semantics of the (define id expr) form is the same as in Racket. In ErdaRVM functions are not first class, and the language does not include a lambda form, and thus this form is intended only for defining variables (that do not name functions).

The second form, which is for defining “regular” ErdaRVM functions, binds id as a function that takes arguments arg .... The language enforces that the arguments will all have to be good, wrapped values (i.e., values for which the predicate good-result? holds); there is an implicit alert guarding against bad values. Explicit alerts may be specified according to the maybe-alerts grammar. The result of the function should be a single wrapped value (i.e., values for which result? holds). Indeed, ErdaRVM does not support multi-value returns, or more generally, multi-value expressions. The language enforces, on the single return value, the data invariant associated with its type; the function application will produce a bad value instead if the invariant does not hold. Explicit post-conditions are treated similarly. The invariants are not checked on bad results.

The #:handler variant of the define form is like the “regular” function definition form, but without the implicit alerts requiring good arguments, or the assumption that post-condition expressions require good free variables. That is, any pre-conditions get evaluated even if some of the arguments are bad, and if they hold, the function gets called. Similarly, any post-conditions (but not data invariant) are also checked on a bad result. The intention is for this kind of function definition to make it possible to implement “handler” functions able to process (and perhaps recover from) bad values.

The #:direct variant of the form defines a function that is called directly, without the language doing any pre- or post-processing on the incoming or outgoing values, which are still expected to be wrapped (no data invariants is checked either, so beware). This form of define is intended to allow for the implementation of ErdaRVM functions (such that they are aware of wrapped values) as Racket-based primitives, probably using Racket’s #%plain-app form as the “FFI” for implementing such primitives within the ErdaRVM language. Perhaps more likely, you’ll want to implement such functions in Racket, and instead merely declare them as #:direct.

syntax

(declare (id arg ...) maybe-alerts)

(declare (id arg ...) #:direct)
Forms used to specify information about functions, not to implement them, or to bind the identifier id. The binding must already exist.

The first declare form declares a Racket primitive that processes unwrapped values, and thus will get automatic unwrapping/wrapping at the application site. Alert clauses may be specified, with any test-expr evaluated with the arg (and value, as appropriate) identifiers bound to wrapped values; in other words, despite a primitive function being called, the conditional expressions are still written in ErdaRVM. As many existing Racket functions may throw exceptions, it is quite important to specify on-throw alert clauses as appropriate, as a way of converting from such a foreign error reporting mechanism.

The second declare form is like the #:direct define form, but without taking an implementation. An implementation must already be bound as the function id.

It is also possible to call undeclared Racket functions, as long as they are bound. Naturally, then, no explicit alerts have been specified, but goodness of arguments is nonetheless enforced, and arguments are unwrapped automatically; undeclared functions are expected to process unwrapped values. There is no catching of exceptions, but the data invariant of the result value is checked. A broken DI leads to the result being automatically wrapped as a bad value, whereas otherwise it is wrapped as a good value.

1.2.1 Alerts

The alert specification of a defined or declared function matches the following grammar.

  maybe-alerts = 
  | #:alert (alert-clause ...)
     
  alert-clause = (alert-id pre-when test-expr)
  | (alert-id pre-unless test-expr)
  | (alert-id post-when test-expr)
  | (alert-id post-unless test-expr)
  | (alert-id on-throw pred-expr)

where:

alert-id

An alert name. The name of the alert to trigger if the corresponding test-expr holds, or if the corresponding pred-expr predicate holds for a thrown exception object.

test-expr

An ErdaRVM expression, computing in wrapped values. The test-exprs of #:handler functions should probably be able to deal with bad values as well.

The expression is automatically negated for the pre-unless and post-unless cases.

For post-condition expressions, the result of the function application is bound as value.

pred-expr

A Racket predicate expression, computing in bare values. The predicate should accept any bare exn structure (or a subtype) as an argument, and yield a bare value indicating whether the predicate holds.

1.3 Expressions

syntax

(#%datum . datum)

A good literal value, as specified by datum.

For example:
> 0

(Good 0)

syntax

(quote id)

A good symbol id. In ErdaRVM symbols are primarily used to name alerts.

For example:
> 'not-found

(Good 'not-found)

syntax

value

A result value. Bound in the scope of a define or declare post-condition expression, for example, but also in some other syntactic contexts that have expressions for result processing (see try, for example).

syntax

(if test-expr then-expr else-expr)

Like Racket’s if, but processes wrapped values. If test-expr yields a bad value, then the overall expression yields a bad value, and neither branch is evaluated. If test-expr yields a good #f, then the else-expr expression is evaluated. Otherwise then-expr is evaluated.

For example:
> (if (raise 'bad) 'yes 'no)

(Bad bad-arg monadic-if)←(Bad bad raise)

syntax

(if-not test-expr then-expr else-expr)

Equivalent to (if test-expr else-expr then-expr).

syntax

(or expr ...)

Like Racket’s or, but processes wrapped values. If any of the expressions yields a bad value, none of the remaining expressions are evaluated, and the overall result will also be bad.

syntax

(and expr ...)

Like Racket’s and, but processes wrapped values. If any of the expressions yields a bad value, none of the remaining expressions are evaluated, and the overall result will also be bad.

syntax

(cond cond-clause ... else-clause)

 
cond-clause = (test-expr then-expr)
     
else-clause = (else then-expr)
A conditional expression that processes wrapped values. If any of the test-expressions yields a bad value, none of the remaining expressions are evaluated, and the overall result will also be bad. Note the compulsory else clause, which is a significant difference compared to Racket’s cond.

For example:
> (cond
    [#f 'false]
    [(raise 'bad) 'bad]
    [else 'otherwise])

(Bad bad-arg monadic-if)←(Bad bad raise)

syntax

(anti-do ([arg expr] ...) body ...+)

Locally switches to a bare-value “evaluation mode,” for the body expressions. Each argument value, given by expr, is unwrapped and bound to the corresponding arg, which must be an identifier. Said identifiers will be bound in the scope of the body. The result of the body expressions is then again wrapped, in either a good or bad wrapper, based on the DI. The overall expression fails if any expr yields a bad value, and in that case the body is left unevaluated.

syntax

(try body ...+ #:catch catch-clause maybe-catch-all)

 
catch-clause = ((id ...) then-expr ...+)
     
maybe-catch-all = 
  | (_ then-expr ...+)
Evaluates the body expressions, and if the last of them yields a bad result, then matches it against the catch-clauses based on the alert name of the bad value. The optional maybe-catch-all clause will match anything. If the body result is good, or if there is no matching clause, then that result remains the result of the overall expression. Otherwise the result is given by the last then-expr of the first matching clause.

For example:
> (try (raise 'worst)
   #:catch [(bad worse still-worse) 1]
           [(worst) 2]
           [_ 3])

(Good 2)

> (try (raise 'bad) #:catch [(worse worst) 3])

(Bad bad raise)

> (try 1 #:catch [_ 3])

(Good 1)

> (try (raise 'bad) #:catch [_ 3])

(Good 3)

syntax

(default try-expr fail-expr)

Evaluates the try-expr expression, and only if its result is a bad one, then evaluates the fail-expr for the result of the overall expression. Where the result of try-expr is good, that becomes the value of the overall expression.

For example:
> (default 'good 'alternative)

(Good 'good)

> (default (raise 'bad) 'alternative)

(Good 'alternative)

syntax

(on-alert (handler-clause ...) body ...+)

 
handler-clause = ((id ...) expr ...+)
Installs handlers for the scope of the body expressions, the last of which normally gives the result of the overall expression.

If any application of a function listed by id fails (with a bad result), then a matching clause’s expressions are evaluated, and the result of the last of them is substituted in place of the result of the failed function call.

This recovery mechanism does not apply to syntactic forms (even if named by id), nor will recovery happen within the body of an anti-do expression.

For example:
> (on-alert () 'nothing)

(Good 'nothing)

> (on-alert ([(not) 'good]) (raise 'bad))

(Bad bad raise)

> (on-alert ([(raise) 'good]) (raise 'bad))

(Good 'good)

syntax

(block stat ... result-expr)

 
stat = (#:let id expr)
  | (#:when test-expr #:let id expr)
  | expr
Evaluates a sequence of restricted “statements,” in the order given. Each stat may be an assignment, a conditional assignment, or an expression. Conditional assignment only happens if the condition is a good true value. What appears to be assignment to a previously defined variable is actually a shadowing single static assignment. Any id that gets bound is in scope for the rest of the expression. A restriction of conditional assignment is that conditional assignment to an unbound id is not allowed, as then id might not be bound for the rest of the expression.

The evaluation of the overall expression immediately stops with a bad value if a test-expr produces a bad value. Where there were no failures in conditionals, the overall result of the expression will be that of result-expr.

For example:
> (block 1 2 3)

(Good 3)

> (block [#:let x 1] x)

(Good 1)

> (block [#:let x 1] [#:let x 2] x)

(Good 2)

> (block [#:let x 1] [#:when #t #:let x 2] x)

(Good 2)

> (block [#:let x 1] [#:when #f #:let x 2] x)

(Good 1)

> (block [#:let x (raise 'bad)] [#:when x #:let x 'good] x)

(Bad bad-arg monadic-if)←(Bad bad raise)

1.4 Standard Library

This section lists a small selection of the ErdaRVM standard library.

The documented argument and result types (or predicates, rather) are only for informational purposes; they are not necessarily enforced using actual contracts (indeed the language does not have support for contracts built-in).

Some functions do have pre- and post-conditions specified with alert clauses, but these are not indicated in the signatures shown here; the signatures here reflect the functions’ own ability to handle inputs. It is the ErdaRVM language itself that does further enforcing, according to explicit or implicit alert conditions.

procedure

(result? x)  good-result?

  x : any/c
A predicate that holds if x is a wrapped value (whether good or bad). The result of the predicate is itself wrapped.

procedure

(good-result? x)  good-result?

  x : any/c
A predicate that holds if x is a good (wrapped) value.

procedure

(bad-result? x)  good-result?

  x : any/c
A predicate that holds if x is a bad (wrapped) value.

For example:
> (bad-result? (raise 'worst))

(Good #t)

procedure

(raise alert-name)  bad-result?

  alert-name : good-result?
Creates a new bad value with the specified alert-name, passed in as a wrapped symbol. The constructed bad value will have no history beyond the call to this function.

2 ErdaRVMσ

 #lang erda/sigma-rvm package: erda

The ErdaRVMσ language is a variant of ErdaRVM such that it exports an assignment expression, and modified conditionals with optional cleanup actions. Only the additions are documented here.

ErdaRVMσ’s set! form (for variable assignment) is the same as in Racket. Bad values also get assigned.

The only conditionals available in ErdaRVMσ are if, when, and unless. The other conditionals from ErdaRVM are not available. More or less all other forms (e.g., define) and functions (e.g., raise) from ErdaRVM are also included in ErdaRVMσ.

syntax

(if test-expr then-expr else-expr maybe-cleanup)

 
maybe-cleanup = 
  | #:cleanup cleanup-expr ...
Like ErdaRVM’s if, but may include cleanup actions. Said actions are given as a sequence of cleanup-expr expressions, which are evaluated for their side effects in the case that test-expr yields a bad value; this does not influence the result of the overall expression, which will still be as for ErdaRVM’s if.

For example:
> (define failed? #f)
> (if (raise 'worse) 1 2 #:cleanup (set! failed? #t))

(Bad bad-arg monadic-if/cleanup)←(Bad worse raise)

> failed?

(Good #t)

syntax

(when test-expr body ...+ maybe-cleanup)

Like Racket’s when, but processes wrapped values, and may include cleanup actions. Where test-expr is a good #f value, the result of the overall expression will be #<void>, without any wrapper.

Note that there is no when or unless in ErdaRVM, as having them makes little sense without side effects (such as assignment).

For example:
> (when 1
    2 3)

(Good 3)

syntax

(unless test-expr body ...+ maybe-cleanup)

Like when, but evaluates body expressions in the case where test-expr is a good #f value.

For example:
> (unless 1
    2 3)
> (let ([x 1])
    (when (raise 'worst)
      (set! x 2)
      #:cleanup (set! x 3))
    x)

(Good 3)

3 ErdaC++

 #lang erda/cxx package: erda

The ErdaC++ language is a statically typed language such that it includes an alerts mechanism for declarative error reporting, and transparently propagates errors as data values.

ErdaC++ is very similar to ErdaRVM, but with some notable differences:
  • Provided that any referenced functions are implemented both for Racket and C++, the definitions appearing in a erda/cxx module (or collection thereof) may both be used directly from a Racket program, and translated into a C++ API and implementation usable from C++ programs. In contrast, the definitions appearing in a erda/rvm module are only intended for evaluation in the Racket VM.

  • While the ErdaC++ and ErdaRVM implementations are largely based on the same code, the former does somewhat more work at compile time. This is to keep the runtime requirements smaller, and thus facilitate translation into C++. The only C++ runtime requirements for the erda/cxx language itself are a small subset of the C and C++11 standard libraries, and the "erda.hpp" header file.

  • ErdaC++’s functions and variables are typed, whereas in ErdaRVM it is values that are typed. While the static types need not always be declared, the types of a program must be fully resolvable statically. For this purpose, the compiler features Hindley-Milner style type inference.

  • For declaring types and other details relating to translating ErdaC++ into C++, the language features support for various annotations (e.g., type, foreign, etc.) that may be specified for declarations; there are no such annotations in ErdaRVM.

  • Not everything from ErdaRVM has been brought over to ErdaC++; notably, some of the error recovery supporting forms are missing, as is most of the runtime library. The focus in ErdaC++ has been to include only the essentials in the language, and exclude more experimental features (such as try and on-alert). The idea is to improve and validate the design of these features in ErdaRVM first, before bringing them into other Erda variants.

This document describes the syntax and semantics a selection of those ErdaC++ constructs that have notable differences to ErdaRVM’s. Overall, ErdaC++’s syntactic constructs generally have the same semantics as in ErdaRVM, and we do not document them separately here.

syntax

(define #:type id maybe-annos)

(define id maybe-annos expr)
(define (id arg ...) maybe-annos maybe-alerts expr ...+)
(define (id arg ...) #:handler maybe-annos maybe-alerts expr ...+)
(define (id arg ...) #:direct maybe-annos expr ...+)
Forms used to define types, variables and functions.

These forms have the same semantics as for ErdaRVM’s define, with three notable exceptions. Firstly, there is a define #:type form, which is the same as for Magnolisp’s define. Second, ErdaC++ does not support the on-throw alert clause; the maybe-alerts grammar is otherwise the same as given in the Alerts section. Third, all the define variants accept optional annotations; the grammar for maybe-annos is as described in Annotations.

syntax

(declare #:type id maybe-annos)

(declare (id arg ...) maybe-annos maybe-alerts)
(declare (id arg ...) #:direct maybe-annos)
Forms used to specify information about types and functions, not to implement them, or to bind the identifier id. The binding must already exist.

See define for a description of the three notable differences between ErdaC++’s declare compared to ErdaRVM’s declare and Magnolisp’s declare, as these differences are the same for both define and declare.

3.1 C++ Translation Advising Annotations

Some of the ErdaC++ defining forms support a subset of the annotations that appear in the Magnolisp language. The supported annotations are: type, export, foreign, and literal. The purpose of these annotations is to instruct ErdaC++-to-C++ translation. Refer to Magnolisp documentation for more details about them.

4 Example Code

For sample ErdaRVM code, see the "i1-prog.rkt" file of the Erda implementation codebase. Said code should evaluate as is within the Racket VM; see the racket command of your Racket installation.

For sample ErdaC++ programs, see the "test-*.rkt" files and "program-*" projects in the "tests" directory of the codebase.

Most of the provided sample ErdaC++ programs will evaluate as is within the Racket VM. To instead translate said programs into C++, see the Magnolisp documentation, or look at the "Makefile"s in the "program-*" directories for example invocations of the mglc command-line tool.

To run basic tests to verify that the Magnolisp compiler is available and working, you may run:
  make test

5 License

Except where otherwise noted, the following license applies:

Copyright © 2014-2015 University of Bergen and the authors.

Authors: Tero Hasu

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.