R/brassica_heap.R

Defines functions TypeFromName IsIntegerConstrained RecallFunction DefineFunction TypeOfVariable TypesMatch NumberOfSubscripts NameForObject IsStringValued IsNumberValued IsIntegerValued IsArray GetOrCreateObject DefaultExtent SetValue GetValue DimensionArray Variable SubscriptsOfVariable NameOfVariable SubscriptsMatch RetrieveValue LinearisedIndex ExtentOfObject DimensionalityOfObject AssignVariable InitialDatum CreateObject SetObject ObjectExists GetObject ResetHeapMemory

# Implements storage of BASIC data within an R environment.
# MJL @ Titirangi, 3 August 2022.

################################################################################
# Storage space for BASIC variables, functions, and arrays.

heapMemory <- new.env(parent = emptyenv())

################################################################################
# BASIC-variable initialisation constants.

initialString <- ""     # Initial value for a string-type variable.
initialNumber <- 0      # Initial value for a number-type variable.
defaultExtent <- 10L    # Default extent for each dimension of an array.

################################################################################
# Clear heap memory.

ResetHeapMemory <- function()
{
  # Delete all BASIC data storage objects (un-assigns all variables).
  remove(list = ls(heapMemory), pos = heapMemory)
}

################################################################################
# R-object setter and getter.

GetObject <- function(n)
{
  # Retrieves the storage-object named n, in its entirety.
  get(n, heapMemory)
}

ObjectExists <- function(n)
{
  # Returns TRUE if and only if a storage-object named n exists.
  # Returns FALSE otherwise.
  exists(n, heapMemory, inherits = FALSE)
}

SetObject <- function(n, v)
{
  # Assigns value v to the storage-object named n.
  assign(n, v, heapMemory)
}

################################################################################
# Functions acting upon R storage-object names.

CreateObject <- function(n, d)
{
  # Creates a storage-object of name n and BASIC (array) dimensions d.
  # For a singleton (non-array) object, d should be an empty integer vector.
  v <- InitialDatum(n)
  SetObject(n, if (length(d) == 0L) v else array(v, d + 1L))
}

InitialDatum <- function(n)
{
  # Returns the value to which the storage-object named n is initialised at the
  # time of its creation.
  if (TypeFromName(n) == basicS) initialString else initialNumber
}

################################################################################
# Functions acting upon R BASIC-data storage objects.

AssignVariable <- function(x, v, a)
{
  # Assigns BASIC-value a to the BASIC variable (or array element) referenced by
  # v and stored as R object x (or an element thereof).
  b <- if (IsIntegerValued(v)) floor(Value(a)) else Value(a)
  x[LinearisedIndex(x, v)] <- b
  SetObject(NameForObject(v), x)
}

DimensionalityOfObject <- function(x)
{
  # Returns the integer number of dimensions of storage-object x (zero, in the
  # case of a singleton non-array object).
  length(dim(x))
}

ExtentOfObject <- function(x)
{
  # Returns a vector of the (zero-indexed) BASIC array dimensions spanned by
  # (one-indexed) R storage-object x. That is, a return value of c(2, 3) means
  # x stores a BASIC array of those dimensions, e.g., A(2,3). These differ from
  # the R dimensions of x. Returns an empty vector when x is not an array.
  d <- dim(x)
  if (is.null(d)) integer() else d - 1L
}

LinearisedIndex <- function(x, v)
{
  # Maps an n-dimensional set of BASIC-variable reference (v) subscripts, each
  # starting at zero, to a single R storage-object (x) index, starting from one.
  # Includes zero-dimensional singleton (non-array) objects and variables.
  d <- ExtentOfObject(x)
  sum(cumprod(c(1L, d[-length(d)] + 1L)) * SubscriptsOfVariable(v)) + 1L
}

RetrieveValue <- function(x, v)
{
  # Extracts the BASIC value referenced by variable v from storage-object x.
  # Variable subscript validity checks should already have been performed.
  a <- x[LinearisedIndex(x, v)]
  if (IsStringValued(v)) AsString(a) else AsNumber(a)
}

SubscriptsMatch <- function(x, v)
{
  # Checks whether or not a storage-object, x, and a BASIC variable reference,
  # v, have the same number of dimensions (subscripts) and that x is large
  # enough to encompass the specific subscripts of v. Returns TRUE if and only
  # if this is the case, or FALSE otherwise. Subscripts must be evaluated to
  # integers before this is called.
  ((DimensionalityOfObject(x) == NumberOfSubscripts(v))
    && all(ExtentOfObject(x) >= SubscriptsOfVariable(v)))
}

################################################################################
# BASIC-variable reference-object constructor and getters.

NameOfVariable <- function(v)
{
  # Returns the name of the BASIC variable referenced by v, as a character
  # string. This includes the trailing $ or % in the case of string or integer
  # variables, respectively. Does not include the parenthesis or subscripts of
  # array-element references (only the name of the whole array).
  v[["name"]]
}

SubscriptsOfVariable <- function(v)
{
  # Returns the subscripts of BASIC variable reference v as either an integer
  # vector (of evaluated subscripts) or a character vector (of unevaluated BASIC
  # expressions). This is empty when the variable is dimensionless (has no
  # subscripts).
  v[["subscripts"]]
}

Variable <- function(n, i)
{
  # Constructs a BASIC variable reference object, for a variable of name n and
  # subscripts i. For singleton (non-array) references, i should be empty.
  # The subscripts, i, may be evaluated (to integers) or unevaluated strings
  # of BASIC expressions (ordinarily the latter). Besides the usual data-typed
  # variables, A, A$, etc., we also use these constructs for user-defined
  # functions, FNA(X). These have the same name-subscripts-value description,
  # and are indeed variable, but the subscripts need not be integers and the
  # (string) value is evaluated as code, rather than being taken at face value.
  list(name = unname(n), subscripts = i)
}

################################################################################
# Initialisation, assignment and recall of BASIC variables.

DimensionArray <- function(v)
{
  # Initialises a BASIC array of the name and dimensions in reference v.
  n <- NameForObject(v)
  if (ObjectExists(n)) return(SetRedimmedArrayError())
  i <- EvalSubscripts(SubscriptsOfVariable(v))
  if (Terminated()) return(Exit())
  CreateObject(n, i)
}

GetValue <- function(v)
{
  # Returns the value of the BASIC variable (or array element) referenced by v.
  # A default value will be created if the variable has not yet been assigned.
  v <- Variable(NameOfVariable(v), EvalSubscripts(SubscriptsOfVariable(v)))
  if (Terminated()) return(Exit())
  x <- GetOrCreateObject(v)
  if (!SubscriptsMatch(x, v)) return(SetBadSubscriptError())
  RetrieveValue(x, v)
}

SetValue <- function(v, a)
{
  # Assigns BASIC value a to the BASIC variable (or array element) referenced by
  # v. Returns a.
  v <- Variable(NameOfVariable(v), EvalSubscripts(SubscriptsOfVariable(v)))
  if (Terminated()) return(Exit())
  x <- GetOrCreateObject(v)
  if (!SubscriptsMatch(x, v)) return(SetBadSubscriptError())
  if (!TypesMatch(v, a)) return(SetTypeMismatchError())
  AssignVariable(x, v, a)
  a
}

################################################################################
# Functions acting upon BASIC-variable references.

DefaultExtent <- function(v)
{
  # Returns default BASIC array dimensions for initialising the BASIC variable
  # referenced by v when it has not already been defined by a DIM statement.
  # Returns and empty integer vector when v is a singleton (not an array).
  rep(defaultExtent, NumberOfSubscripts(v))
}

GetOrCreateObject <- function(v)
{
  # Retrieves the entirety of the R storage-object for the BASIC variable or
  # array referenced by v. If the storage-object does not already exist, it will
  # be created (with default dimensions and values) beforehand.
  n <- NameForObject(v)
  if (ObjectExists(n)) GetObject(n) else CreateObject(n, DefaultExtent(v))
}

IsArray <- function(v)
{
  # Returns TRUE if and only if the BASIC variable referenced by v is an element
  # of a BASIC array. Returns FALSE otherwise.
  NumberOfSubscripts(v) != 0L
}

IsIntegerValued <- function(v)
{
  # Returns TRUE if and only if the BASIC variable referenced by v is an integer
  # constrained number. Returns FALSE otherwise.
  IsIntegerConstrained(NameOfVariable(v))
}

IsNumberValued <- function(v)
{
  # Returns TRUE if and only if the BASIC variable referenced by v is a number.
  # Returns FALSE otherwise.
  TypeOfVariable(v) == basicN
}

IsStringValued <- function(v)
{
  # Returns TRUE if and only if the BASIC variable referenced by v is a string.
  # Returns FALSE otherwise.
  TypeOfVariable(v) == basicS
}

NameForObject <- function(v)
{
  # Maps the name of the BASIC variable referenced by v to the name of its R
  # data-storage object. In BASIC, X and X(1) are two different objects. We
  # prepend a marker to array names, rather than appending a '(', in order to
  # keep variable data-type checks simple (these look for a trailing '$').
  if (IsArray(v)) paste0("_", NameOfVariable(v)) else NameOfVariable(v)
}

NumberOfSubscripts <- function(v)
{
  # Returns the number of subscripts (dimensions) of BASIC variable reference v.
  length(SubscriptsOfVariable(v))
}

TypesMatch <- function(v, a)
{
  # Returns TRUE if and only if the BASIC data type of the variable (reference)
  # v matches that of the BASIC value a. Returns FALSE otherwise.
  TypeOfVariable(v) == Type(a)
}

TypeOfVariable <- function(v)
{
  # Returns the BASIC data type (string or number) of variable (reference) v.
  TypeFromName(NameOfVariable(v))
}

################################################################################
# Define and recall user-defined BASIC functions.

DefineFunction <- function(f, p, s)
{
  # Define a function, named f, of parameter variables named p, as the BASIC
  # expression of character string s. The parameters must all be singletons (no
  # arrays), and we store them by the names of their corresponding R-objects.
  # To distinguish the function from data-type objects, its name, f, necessarily
  # begins with 'FN' (or 'fn', etc.).
  n <- sapply(p, function(v) NameForObject(Variable(v, character())))
  SetObject(f, list(parameters = n, definition = s))
}

RecallFunction <- function(f)
{
  # Returns the definition of user-defined function named f. To distinguish the
  # function from data-type objects, its name, f, necessarily begins with 'FN'
  # (or 'fn', etc.).
  if (!ObjectExists(f)) return(SetUndefinedFunctionError())
  GetObject(f)
}

################################################################################
# Type utilities, acting on any name(s) (variable, function, or storage-object).

IsIntegerConstrained <- function(n)
{
  # Returns TRUE when string n names an integer-constrained variable, R storage
  # object, or user-defined function. Returns FALSE otherwise.
  substring(n, nchar(n)) == "%"
}

TypeFromName <- function(n)
{
  # Returns the data-type of a variable, or the return type of a user-defined
  # function, from either its name, or the associated R-object name, n. In
  # general, n can be a vector of names.
  c(basicN, basicS)[as.integer(substring(n, nchar(n)) == "$") + 1L]
}

############################################################################ EOF

Try the brassica package in your browser

Any scripts or data that you put into this service are public.

brassica documentation built on Oct. 24, 2022, 9:07 a.m.