src.org.kiama.machine.Machine.scala Maven / Gradle / Ivy
/*
* This file is part of Kiama.
*
* Copyright (C) 2008-2012 Anthony M Sloane, Macquarie University.
*
* Kiama is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Kiama is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Kiama. (See files COPYING and COPYING.LESSER.) If not, see
* .
*/
package org.kiama
package machine
import java.lang.Exception
import scala.collection.mutable.HashMap
import org.kiama.util.Emitter
import org.kiama.output.PrettyPrinter
/**
* A deterministic abstract state machine defined by its main rule and
* called `name`. Tracing messages are output to the given emitter, which
* defaults to standard output.
*/
abstract class Machine (val name : String, emitter : Emitter = new Emitter)
extends PrettyPrinter {
import language.implicitConversions
/**
* Debug flag. Set this to true in sub-classes or objects to obtain
* tracing information during execution of the machine.
*/
def debug = false
/**
* A scalar item of abstract state machine state holding a value of
* type `T` and called `sname`.
*/
class State[T] (val sname : String) {
/**
* The value of this item of state. `None` means undefined.
*/
protected var _value : Option[T] = None
/**
* Is this state item undefined or not?
*/
def isUndefined : Boolean = _value.isEmpty
/**
* Make this state item undefined.
*/
def undefine =
_value = None
/**
* Return the value of this state item if it's defined. Otherwise
* abort execution.
*/
def value : T =
_value match {
case None =>
sys.error ("State.value: " + name + "." + sname + " is undefined")
case Some (t) =>
t
}
/**
* Update this item of state to the value `t`. The update is actually
* delayed until the end of the step when all updates in that
* step happen simultaneously (along with consistency checking). The
* state value only becomes defined when this latter process happens.
*/
def := (t : T) {
updates = new ScalarUpdate (this, t) :: updates
}
/**
* Change this item of state to the value `t`. The change occurs
* immediately.
*/
def change (t : T) =
_value = Some (t)
/**
* Equality on the underlying value. If this state item is undefined
* then it's not equal to anything.
*/
def =:= (t : T) : Boolean =
if (isUndefined)
false
else
_value == Some (t)
/**
* Make a printable representation for the contents of this state
* object.
*/
override def toString : String =
_value match {
case None => "** undefined **"
case Some (t) => t.toString
}
}
/**
* Implicitly allow a scalar state value of type `T` to be used as a value
* of type `U` where `U` is a supertype of `T`.
*/
implicit def stateTToT[T,U >: T] (t : State[T]) : U = t.value
/**
* Utility class for updaters for values of parameterised state.
*/
class ParamUpdater[T,U] (val state : ParamState[T,U], val t : T) {
/**
* Update this item of state to the value `u` at parameter `t`. The update
* is actually delayed until the end of the step when all updates in that
* step happen simultaneously (along with consistency checking). The
* state value only becomes defined when this latter process happens.
*/
def := (u : U) = {
updates = new ParamUpdate (state, t, u) :: updates
}
/**
* Equality on the underlying value. If this state item is undefined
* then it is not equal to anything.
*/
def =:= (u : U) : Boolean =
if (state.isUndefined (t))
false
else
state.value (t) == u
/**
* Make a printable representation of the value of the parameterised
* state item that would be updated. Used particularly to access the
* value of a parameterised state item.
*/
override def toString : String =
state.value (t).toString
}
/**
* A parameterised item of abstract state machine state holding values
* of type `U`, associated with parameters of type `T`.
*/
class ParamState[T,U] (val psname : String) extends State[HashMap[T,U]] (psname) {
/**
* Is this state item undefined at `t` or not ?
*/
def isUndefined (t : T) : Boolean =
_value match {
case None => true
case Some (m) => ! (m contains t)
}
/**
* Make this state item undefined at `t`.
*/
def undefine (t : T) =
_value match {
case None => // Nothing to undefine
case Some (m) => _value = Some (m - t)
}
/**
* Return an updater for the value at parameter `t`. Used as `s(t)`
* this will return the value in the state `s` at parameter `t`. Used
* as `s(t) := u` this will update the value to `u` at parameter `t`.
* The update is actually delayed until the end of the step when all
* updates in that step happen simultaneously (along with consistency
* checking). The state value only becomes defined when this latter
* process happens.
*/
def apply (t : T) = new ParamUpdater (this, t)
/**
* Return the value of this state item if it's defined at parameter `t`.
* Otherwise abort execution.
*/
def value (t : T) : U =
_value match {
case None =>
sys.error ("ParamState.value: " + name + "." + sname +
" is undefined")
case Some (m) if m contains t =>
m (t)
case _ =>
sys.error ("ParamState.value: " + name + "." + sname + "(" + t +
") is undefined")
}
/**
* Change this item of state to the value u at parameter `t`. The
* change occurs immediately.
*/
def change (t : T, u : U) =
_value match {
case None => _value = Some (HashMap ((t, u)))
case Some (m) => m += ((t, u))
}
}
/**
* Allow an updater to be used to access a parameterised state value.
*/
implicit def paramUpdaterToU[T,U,V >: U] (up : ParamUpdater[T,U]) : V =
up.state.value (up.t)
/**
* An update of an item of state `s` to have the value `t`.
*/
abstract class Update {
/**
* Perform this update
*/
def perform
/**
* Return a key for use when checking consistency of this update
* with other updates. Must uniquely determine the state that is
* being updated.
*/
def key : AnyRef
/**
* Return the value to which the state is being updated.
*/
def value : Any
}
/**
* An update of a scalar item of state `s` to have the value `t`.
*/
class ScalarUpdate[T] (s : State[T], t : T) extends Update {
/**
* Perform this update.
*/
def perform {
s.change (t)
if (debug) {
val d = name <> '.' <> s.sname <+> ":=" > nest (s.toDoc)
emitter.emitln (pretty (d))
}
}
/**
* Return a key for use when checking consistency of this update
* with other updates. Must uniquely determine the state that is
* being updated.
*/
def key : AnyRef = s
/**
* Return the value to which the state is being updated.
*/
def value : Any = t
/**
* Convert to string representation. Really only used when
* printing inconsistent state exceptions.
*/
override def toString : String =
name + "." + s.sname + " := " + t
}
/**
* An update of a parameterised item of state `s` to have the value `u`
* at parameter `t`.
*/
class ParamUpdate[T,U] (s : ParamState[T,U], t : T, u : U) extends Update {
/**
* Perform this update.
*/
def perform {
s.change (t, u)
if (debug) {
val d = name <> '.' <> s.sname <> '(' <> t.toDoc <> ')' <+>
":=" > nest (u.toDoc)
emitter.emitln (pretty (d))
}
}
/**
* Return a key for use when checking consistency of this update
* with other updates. Must uniquely determine the state that is
* being updated.
*/
def key : AnyRef = (s, t)
/**
* Return the value to which the state is being updated.
*/
def value : Any = u
/**
* Convert to string representation. Really only used when
* printing inconsistent state exceptions.
*/
override def toString : String =
name + "." + s.sname + "(" + t + ") := " + u
}
/**
* The updates for the current step of execution of this machine.
*/
private var updates : List[Update] = Nil
/**
* Initialise the state of this machine. This routine is called
* before the first step of the machine is attempted. Any pending
* state updates will be performed after this routine returns.
* Default: do nothing.
*/
def init = { }
/**
* The rule to execute to run one step of this machine.
*/
def main
/**
* Clean up after this machine. This routine is called after the
* machine terminates. Default: do nothing.
*/
def finit = { }
/**
* Perform any pending updates, returning true if updates were
* performed and false otherwise. The updates are first checked
* for consistency. If the same piece of state is updated more
* than once, it must be updated to the same value by all updates.
* If updates are not consistent, the machine is aborted.
*/
def performUpdates =
if (updates.isEmpty)
false
else {
// Check updates for consistency
val m = new HashMap[AnyRef,Any]
for (u <- updates) {
val k = u.key
if (m contains k) {
if (m (k) != u.value)
throw new InconsistentUpdateException (this, u, m (k))
} else {
m (k) = u.value
}
}
// Actually perform the updates
updates.map (_.perform)
true
}
/**
* Reset the machine to begin a step.
*/
def reset = {
updates = Nil
}
/**
* Perform a step of this machine. Return true if some updates were
* made or false if none.
*/
def step : Boolean = {
reset
main
performUpdates
}
/**
* Execute the steps of this machine. Halt when a step makes no
* updates. `init` should be called before this method.
*/
def steps {
var nsteps = 0
do {
if (debug)
emitter.emitln (name + " step " + nsteps)
nsteps += 1
} while (step)
}
/**
* Run this machine by initialising its state and then executing
* its steps.
*/
def run {
init
performUpdates
steps
finit
}
}
/**
* A machine has performed an inconsistent update in the sense that a step
* has updated an item of state to two different values. `m` is the machine
* that performed the update, `u` is the update, and `v` is the value used by
* another update of the same state item in that step.
*/
class InconsistentUpdateException[T] (m : Machine, u : Machine#Update, v : T)
extends Exception ("Machine = " + m.name + ", update = " + u + ", other value = " + v)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy