AsmGofer is an advanced Abstract State Machine (ASM) programming system, which runs under several platforms (Unix-based or MS-based operating systems). The aim of the AsmGofer system is to provide a modern ASM interpreter embedded in the well known functional programming language Gofer. Gofer is a subset of Haskell -- the de-facto standard for strongly typed lazy functional programming languages. The purpose of this short introduction is to give a brief overview of the main features of AsmGofer and to describe its use:
type Node = Int succs :: Node -> {Node}The range of the successor function is defined using the data structure set, i.e. {Node}. A set is a special version of the commonly used data structure list. An example graph might be a graph that connects 1 to 2 and 3 (i.e. the successors are described by the set {2,3}), 2 to 3 and 4, 3 to 2, 4 to 5, 6 to 1 and for any other node there are no successors.
succs 1 = {2,3} succs 2 = {3,4} succs 3 = {2} succs 4 = {5} succs 6 = {1} succs _ = {}When traversing the graph, we have to remember the visited nodes. We store them in a dynamic function, which is initially nowhere defined (i.e. described by the empty association list).
visited :: Dynamic (Node -> Bool) visited = initAssocs "visited" []Remark: the name "visited" is only used for error reporting. This will be eliminated in the future.
todo :: Dynamic [Node] todo = initVal "todo" [1]The traversal of the graph is performed by the following rule called traverse (++ is list concatenation, head and tail select the head and tail of the list.)
traverse :: Rule() traverse = if todo == [] then skip else if not(visited(head todo)) then do visited(head todo) := True todo := expr2list(succs(head todo)) ++ tail todo else todo := tail todoNotice the absence of syntactic sugar - AsmGofer is, by design, rather terse. There are no mandatory signatures, although the language is strongly typed. For reasons of readablity we nevertheless gave signatures such as
succs:: Node -> {Node}There are no semicolons at the end of definitions - the parsing algorithm makes intelligent use of layout. Function application is just denoted by juxtaposition, instead of writing
f(x)we wrote
f x
asmgofer [file]where `file' (an optional parameter) is the pathname of a file containing an AsmGofer script. We assume that we have stored the previous definitions in a file called "traverse.gs" and that we have called asmgofer with the latter. After parsing and context checking the prompt
?occurs, indicating that you are talking to the AsmGofer command interpreter. This activity is called a ``session''. The basic action is to evaluate expressions, supplied by the user at the terminal, in the environment established by the current script. For example evaluating the dynamic function
todoin the context of the given script produces
? todo [1]The first line shows the result, the last line shows the number of reductions and and storage cells needed. The set of names in scope at any time are those of all currently loaded scripts, together with the names of the standard environment (this is the asmgofer prelude), which are always inscope. For instance, we can also evaluate
(6 reductions, 29 cells)
? todo ++ todo [1,1]Remark: Alternatively we could have written
(13 reductions, 42 cells)
$$ ++ $$since $$ always denotes the value of the last evaluation. If we want to fire the traverse rule once, we evaluate
? fire1(traverse)which means that the ASM described by the loaded script has made one (non-visible) step -- the result is the empty tuple. Notice that you have to evaluate rules. You can not fire rules by themselves. You can inspect the extension of the visited state as an association list by entering
() (42 reductions, 99 cells, 1 steps)
? assocs(visited)If we want to run the ASM until a fixpoint occurs, you enter:
[(1,True)] (7 reductions, 28 cells)
? fixpoint(traverse)For more functions see, the appendix.
(208 reductions, 448 cells, 6 steps)
:load <filename> | load scripts from specified files, clear all files except prelude |
:load | load last specified files, clear all files except prelude |
:also <filename> | load additional scripts files |
:reload | repeat last load command |
:project <filename> | use project file <filename> |
:project | use last project |
:edit <filename> | edit file |
:edit | edit last file |
:type <expr> | print type of expression |
:? | display this list of commands |
:set | set command line options |
:set | help on command line options |
:names [pat] | list names currently in scope |
:info | describe named objects (the *pattern matches any string) |
:find <name> | edit file containing definition of name |
:!command | command shell escape |
:cd dir | change directory |
:gc | force garbage collection |
:quit | exit AsmGofer interpreter |
export GOFER=/usr/AsmGofer2/Preludes/prelude-asm export EDITLINE=vi +%d %s
<expr> ::= ... | forall <quals> do <exp> | choose <quals> do <exp> | create <var>,...,<var> <expr> | <exp> := <exp> | skipThe types are
skip :: Rule () (:=) :: AsmTerm a => a -> a -> Rule () forall <quals> do <exp> :: Rule () choose <quals> do <exp> :: Rule () create <var>,...,<var> <expr> :: Rule ()The Rule type is builtin. It guarantees parallel evaluation. The effect of a rule is only visible if rules are fired! Suppose we want to reset the state in our previous example. Then we could write:
? fire1 (do todo := {1} forall x <-: dom(visited) do visited(x) := false )Note that the already existing "do" works as block...endblock. The primitive choose among is not supported in the current release. There is already an experimental version of it available, but it is not yet stable.
data Where = N | E | W | S data Colour = Red | Orange | Green trafficLights :: Where -> Colour trafficLights = initAssocs "trafficLights" [(x,Orange) | x <- [N,E,W,S]] instance AsmTerm Where instance AsmTerm ColourNow you can write:
? fire1 (do trafficLights(N) := Red trafficLights(N) := Orange)which is -- as expected -- an inconsitent update. Note that you can use the value asmDefault to delete something, e.g.
ERROR: inconsistent update *** dynamic function : "trafficLights" *** expression (new) : Orange *** expression (old) : Red
? fire (do trafficLights(N):= asmDefault)Alternatively you can say that a normal value should play the role of asmDefault, i.e. instead of specifying that any trafficLight is Orange you say that Orange is the default. If you change the running example to
()
? assocs trafficLights
[(East,Orange),(W,Orange),(S,Orange)]
trafficLights :: Where -> Colour trafficLights = initAssocs "trafficLights" [](Asm)Gofer Declarations:
instance AsmTerm Where instance AsmTerm Colour where asmDefault = Orangeyou can evaluate
? trafficLights(N)
Orange