The Osiris Programming Language: Background


 

Background

Last year we made the decision to return to our passion for embedded solutions development and are working with another company to deliver the software for their range of digitally controlled guitar amplifiers and effects..

With the freedom use development tools of our choice on the agreed hardware platform (ARM Cortex-M3/M4), we looked at a number of options before settling on C. We considered Java ME Embedded 8 for the ARM platform but its resource requirements meant that we would need to add an external Flash ROM to the board design and only leave us with 60K of the available 256K RAM for our applicaton code. Furthermore, we were already using C on another project based on the Atmel AVR processor family and we wanted to be able to leverage the audio controller library code we had developed for this platform. Apart from saving time and effort for the new project, it would be helpful if we could maintain a single codebase going forwards - as far as possible bearing in mind the significant differences in processor architectures.

Why C and not C++? We could use C++ for the new ARM boards but the C++ implementation for the 8 bit AVR is very restricted and, as we would like to be able to share as much code as possible between the two processor platforms, C becomes the common denominator. However, as much as we love C, we've spent nearly 25 years writing Object-Oriented (OO) systems and have first-hand experience of its benefits in terms of productivity, reliability and maintainability. With C++ and Java out of the running, we needed a high-level OO language that generated compact, efficient code. This led us to consider reusing the Anubis persistent Object-Oriented programming language that was designed and implemented as as a final year undergraduate project at the University of St. Andrews. Anubis compiles to C, a bit like the original Cfront C++ compiler, which makes integration with our existing code libraries a lot easier and as it was designed for late 1980s processors (Motorola 68030, Sun SPARC 1 & Intel 80386), it is an ideal starting point for use with today's resource constrained microcontrollers:


Relative processor performance:

  • Intel 80386 - 4.3 MIPS
  • Motorola (now Freescale) 68030 - 7 MIPS
  • Sun SPARC 1 - 12.5 MIPS
  • Atmel AVR ATmega328 - 20 MIPS
  • Freescale Kinetis K64 (ARM Cortex-M4) - 150 MIPS


Although the AVR, and especially the K64 (ARM), have more computing power than the '80s processors, they have extremely limited memory resources (2KB and 256KB of RAM respectively). The AVR is interesting because it has 32KB of Flash memory where the program code is stored, and because it is a Harvard architecture processor, this is accessed independently of the RAM, making the total space available for solutions 34KB (program and data). However, this is still extremely resource constrained in comparison to an '80s desktop or workstation (4-16MB RAM) and the language runtime memory footprint is more important than the raw execution speed. This tradeoff is the focus of our new programming language Osiris that is based on Anubis.


Anubis Overview


 

It's worth discussing Anubis before dealing with the changes needed to support embedded systems and the Internet of Things (IoT) that have driven the implementation of the new language Osiris. As Anubis was a undergraduate project, it was subjected to a high degree of academic rigour, which I hope to convince you resulted in making Anubis a much better and simpler language to use.

Before the design of the language began in earnest, a review of some existing OO languages (Smalltalk-80, Eiffel, O2, C++ & Objective-C) was made to understand their unique contributions and to gain a wider understanding OO programming. For example, the implications of the completely Object-Oriented model of Smalltalk versus the multiple base types and constructors model of C++ - the elegance of a simple consistent model / type heirarchy against possible issues with grounding the type recursion without using separate base types.

The team (Richard Loxely (Gibbs), Andrew Tindale and myself) discussed the important features of an Object-Oriented programming language and agreed the following list of design aims for Anubis[1] :

  • The language should use simple semantic and syntactic models
  • The syntax should follow a natural English language format
  • The language should be statically type checked as far as possible
  • The Object-Oriented model should be simple, yet powerful enough to enable the full range of OO modelling techniques to be used
  • The language should support a persistent store, to provide a type secure method of storing program data and sharing such data between programs
To help ensure that Anubis met these design aims, the following three key S-algol language design principles[2] were applied:

  • The principle of correspondence
  • The principle of abstraction
  • The principle of data type completeness

Prof. Ron Morrison at St. Andrews University explained these principles in his PhD thesis:

The principle of data type completeness states that the rules for using data types must be complete with no gaps. This does not mean that all operators in the language need be defined on all data types but rather that general rules have no exceptions. Examples of lack of completeness can be seen in Pascal, for example, where only some data types are allowed as members of sets. The Principle of Data Type Completeness is bound to lead to simpler languages since it avoids the complexity of special cases.

Abstraction is a process of extracting the general structure to allow the inessential details to be ignored. It is a facility well known to mathematicians and programmers since it is usually the only tool they have to handle complexity. The principle of abstraction when applied to language design is invoked by identifying the semantically meaningful syntactic categories in the language and allowing abstractions over them. The most familiar form of abstraction is the function which is an abstraction over expressions.

Finally, the principle of correspondence states that the rules for introducing and using names should be the same everywhere in a program. In particular there should be a one to one correspondence between introducing names in declarations and introducing names as parameters.

These principles are extremely important as they drive the simplicity and power found in S-algol, Anubis and Osiris. For example, there are no restrictions on what types can be assigned to identifiers, passed to procedures or returned from functions. One would expect this to include objects in an OO language but it is less common to find that procedures / functions have the same rights (are first-class citizens). All functional programming languages (e.g. ML, Haskell, Scala, Scheme & Lisp) and a number of scripting languages (e.g. JavaScipt, Perl, Python, PHP & Lua) have first-class functions and a similar concept was introduced in Java 8 as lambda expressions.

We'll discuss the effect of these principles on shaping the design of Anubis and Osiris in a later post but if you can't wait and want to read more detail about the underlying design decisions, including the type, object, inheritance and persistent store models, have a look at the Anubis Language Report: Section 3 - Design.


It's difficult to get a feel for a programming language just by describing its features, so I'll leave you for now with a short Osiris code example that includes the use of first-class procedures (lambda expressions):


Osiris example

class PageHandler( renderPage(), setPreRenderAction(proc(string)), setPostRenderAction(proc(string)) ) is 
begin
	let message = "Do something useful here...\n"

	# These need to be variable as we are going to override them
	let preRenderPageAction := proc(string s); { write "\nDefault pre-render action: " ++ s }
	let postRenderPageAction := proc(string s); { write "\nDefault post-render action: " ++ s }
	
	# This is the method that actually does the work
	let renderPage = proc()
	begin
		preRenderPageAction(message)

		# This is where we would do the interesting stuff - call lots of protected (internal) methods etc.
		write "\n\t[This is where we do the rendering]\n"

		postRenderPageAction(message)
	end

	let setPreRenderAction = proc(proc(string) theProc); preRenderPageAction := theProc
	let setPostRenderAction = proc(proc(string) theProc); postRenderPageAction := theProc
	
end


# Program proper

# Declare my PageHandler variable
let myPageHandler = PageHandler

# Call the default renderPage - pre/post page actions do nothing
write "\n\t\t**** Default renderPage() ****\n"
myPageHandler.renderPage()

# Define my handlers
let myPreRenderFunc = proc(string s)
begin
	write "\nMy pre-render action: " ++ s
end

let myPostRenderFunc = proc(string s)
begin
	write "\nMy post-render action: " ++ s
end

# Now set the actions into myPageHandler
myPageHandler.setPreRenderAction(myPreRenderFunc)
myPageHandler.setPostRenderAction(myPostRenderFunc)

# ...and call the customised renderPage
write "\n\t\t**** Custom renderPage() ****\n"
myPageHandler.renderPage()


We'll return to this example in a later post as it highlights some of the power, yet simplicity, of Osiris.


References
1. R. Gibbs, M. Jamieson, A. Tindale, 1991. Anubis Language Report: Section 3 - Design. Unpublished BSc. (Hons.) dissertation. University of St. Andrews.  
2. S-algol Wikipedia article.