Code to convert base**exp to pow(base,exp) - metrumresearchgroup/mrgsolve GitHub Wiki

convert_pow <- function(code) {
  # Process line by line
  lines <- strsplit(code, "\n")[1](/metrumresearchgroup/mrgsolve/wiki/1)
  converted <- sapply(lines, convert_line)
  paste(converted, collapse = "\n")
}

convert_line <- function(line) {
  # Repeatedly apply conversion until no more ** remain
  while (grepl("\\*\\*", line)) {
    line <- convert_one_pow(line)
  }
  line
}

convert_one_pow <- function(line) {
  # Find the position of the FIRST ** operator
  pos <- regexpr("\\*\\*", line)
  if (pos == -1) return(line)

  op_start <- as.integer(pos)      # position of first *
  op_end   <- op_start + 1L        # position of second *

  # --- Extract BASE (scan LEFT from op_start - 1) ---
  base_end <- op_start - 1L
  base_start <- scan_left(line, base_end)

  # --- Extract EXPONENT (scan RIGHT from op_end + 1) ---
  exp_start <- op_end + 1L
  exp_end <- scan_right(line, exp_start)

  base <- substr(line, base_start, base_end)
  exp  <- substr(line, exp_start,  exp_end)

  # Rebuild the line
  before <- substr(line, 1L, base_start - 1L)
  after  <- substr(line, exp_end + 1L, nchar(line))

  paste0(before, "pow(", trimws(base), ", ", trimws(exp), ")", after)
}

# Scan leftward from `pos` to find the start of an expression token.
# Handles: parenthesised groups, identifiers, numbers (with dots/signs).
scan_left <- function(line, pos) {
  chars <- strsplit(line, "")[1](/metrumresearchgroup/mrgsolve/wiki/1)
  i <- pos

  # Skip leading whitespace
  while (i >= 1 && chars[i] == " ") i <- i - 1L

  if (i < 1) return(1L)

  # If we end on ')' scan back to matching '('
  if (chars[i] == ")") {
    depth <- 0L
    while (i >= 1) {
      if (chars[i] == ")") depth <- depth + 1L
      if (chars[i] == "(") depth <- depth - 1L
      if (depth == 0L) break
      i <- i - 1L
    }
    # Now include the function name before '(' if present
    i <- i - 1L
    while (i >= 1 && (grepl("[A-Za-z0-9_]", chars[i]))) i <- i - 1L
    return(i + 1L)
  }

  # Otherwise scan back over identifier/number characters
  # Allow unary minus/plus as part of a standalone numeric literal only
  # when preceded by nothing or an operator
  while (i >= 1 && grepl("[A-Za-z0-9_.]", chars[i])) i <- i - 1L

  # Absorb a leading unary sign if what's before it is an operator / start
  if (i >= 1 && chars[i] %in% c("+", "-")) {
    prev <- i - 1L
    while (prev >= 1 && chars[prev] == " ") prev <- prev - 1L
    if (prev < 1 || chars[prev] %in% c("(", ",", "=", "+", "-", "*", "/")) {
      i <- i - 1L
    }
  }

  i + 1L
}

# Scan rightward from `pos` to find the end of an expression token.
scan_right <- function(line, pos) {
  chars <- strsplit(line, "")[1](/metrumresearchgroup/mrgsolve/wiki/1)
  n <- length(chars)
  i <- pos

  # Skip leading whitespace
  while (i <= n && chars[i] == " ") i <- i + 1L

  if (i > n) return(n)

  # Absorb optional unary sign
  if (chars[i] %in% c("+", "-")) i <- i + 1L

  # Skip whitespace after sign
  while (i <= n && chars[i] == " ") i <- i + 1L

  # If we start on '(' scan forward to matching ')'
  if (i <= n && chars[i] == "(") {
    depth <- 0L
    while (i <= n) {
      if (chars[i] == "(") depth <- depth + 1L
      if (chars[i] == ")") depth <- depth - 1L
      if (depth == 0L) break
      i <- i + 1L
    }
    return(i)
  }

  # Otherwise scan forward over identifier/number characters (incl. function calls)
  while (i <= n && grepl("[A-Za-z0-9_.]", chars[i])) i <- i + 1L

  # If followed by '(' it's a function call — consume through matching ')'
  if (i <= n && chars[i] == "(") {
    depth <- 0L
    while (i <= n) {
      if (chars[i] == "(") depth <- depth + 1L
      if (chars[i] == ")") depth <- depth - 1L
      if (depth == 0L) break
      i <- i + 1L
    }
  }

  i - 1L
}