Building multiple parts of formula strings from different inputs in R I have been building a function that creates more advanced formula snippets using simple formula code. For example, a user may write:
$$Given\ dependent\ variable\ Y,\ and\ indepdentent\ variables\ X\  and \ U$$
$$Argument\ 1:\ Y_t = \alpha_t +  X_t,\ where\ X_t\ \to\  X_t + \sum_{i = -k}^k \Delta X_{t-i}$$
$$Argument\ 2: \ =U_t ,\ where\ U_t \to\ +\sum_{I = 1}^k U_{t-i} $$

in R: y ~ 1 + x becomes y ~ 1 + x + L(diff(x),-k:k) and

in R: ~ u becomes L(u,1:k)

I have built the following but am curious for feedback as (1) the code itself feels lengthy and (2) it is not super intuitive for a user to start Argument 2 with a ~.

buildFormula(Arg1 = y ~ x, data = dat)
#[1] "y ~ 1 + x + L(diff(x),-k:k)"

buildFormula(Arg1 = y ~ x + u + z, data = dat)
#[1] "y ~ 1 + x + u + z + L(diff(x),-k:k) + L(diff(u),-k:k) + L(diff(z),-k:k)"

buildFormula(Arg1 = y ~ x, data = dat, Arg2 = ~ u) 
#[1] "y ~ 1 + x + L(diff(x),-k:k) + L(u,1:k)"

buildFormula(Arg1 = y ~ x, data = dat, Arg2 = ~ u + z) 
#[1] "y ~ 1 + x + L(diff(x),-k:k) + L(u,1:k) + L(z,1:k)"

buildFormula(Arg1 = y ~ x, data = dat, Arg2 = ~u) 
#[1] "y ~ 1 + x + L(diff(x),-k:k) + L(u,1:k)"

buildFormula(Arg1 = y ~ x, data = dat, Arg2 = u + z)
#Error in ... :  
#  object 'u' not found

buildFormula(Arg1 = y ~ x, data = dat, Arg2 = u + z)
#Error in ... : 
#  object 'u' not found


(3) I would also be appreciative if someone would recommend how to catch this error and warn the user to add a ~ in Arg2.

Function Code

# generate data
dat &lt;- cbind(y = rnorm(30), x = rnorm(30), u = rnorm(30), z = rnorm(30))
# function
buildFormula &lt;- function(Arg1, data, Arg2 = NULL){
  ff &lt;- Arg1
  all_names &lt;- dimnames(attr(terms(ff), "factors"))
  y_names &lt;- all_names[[1]][!(all_names[[1]] %in% all_names[[2]])]
  x_names &lt;- all_names[[2]][all_names[[2]] %in% colnames(data)]
  ff_LHS &lt;- y_names
  # building the right hand side ( ~ X1 + X2 + L(diff(X1),-k:k) + L(diff(X2),-k:k) ) 
  ff_RHS &lt;- paste(c(ifelse(attr(terms(ff), "intercept") == 1, "1", "-1"), # intercept
                    x_names, # independent variables
                    paste0("L(diff(", x_names, "),-k:k)")), # sum of lagged differences of independent variables
                  collapse = " + ")
  # if extra was indicated, add it on but treat it differently (~ + L(X3, 1:k) )
  ff_RHS &lt;- ifelse(is.null(Arg2), ff_RHS, paste0(ff_RHS, " + ", 
                                                   paste0("L(", unlist(strsplit(as.character(Arg2)[-1], " \\+ ")), 
                                                          ",1:k)", 
                                                          collapse = " + ")))
  ff_k &lt;- paste(ff_LHS, "~", ff_RHS)
  ff_k
}
1
The function is a bit lengthy indeed. I have some general remarks, but I will hold them off, because I have some suggestions that would fix points 2 and 3.
2
The source of the error is because u + z is not interpreted as a formula, but rather as the addition of two variables. So when you interact with Arg2, an error is thrown. Adding the ~ fixes this, because it is recognized as a formula then.
A workaround for this problem is to combine both parts of the formula into one. This is something other packages in R do as well. (mlogit::mFormula is one example from the top of my head) The combined formula would look like so: y ~ x | u + z. You can then split the RHS at the | and do your work on each part of the formula.
3
Catching the problem is really quite hard, because any interaction with Arg2 will trigger the evaluation of u+z, which in turn throws an error if u or z don't exist.
What you can do is use tryCatch. This function allows you to evaluate an expression and define a function that is executed if an error or warning is thrown.
&gt; tryCatch(is.formula(1+1), error = function(e)stop(&quot;bla&quot;))
[1] FALSE
&gt; tryCatch(is.formula(u + z), error = function(e)stop(&quot;bla&quot;))
 Error in value[[3L]](cond) : bla 
&gt; tryCatch(is.formula(~u + z), error = function(e)stop(&quot;bla&quot;))
[1] TRUE

You could add this to the top of your function:
if(!tryCatch(is.formula(1+1), 
    error = function(e)FALSE))){
    stop(&quot;Arg2 is not a formula, be sure to add \&quot;~\&quot; to the start.&quot;)
}

Back to 1
String operations will quickly look very wordy, but that's all fine. You are working with words after all.
You spend 4 lines dissecting the formula for the LHS and the RHS. This can be done a lot more easily by coercing the formula to a string:
&gt; as.character(a+b~c+d)
[1] &quot;~&quot;     &quot;a + b&quot; &quot;c + d&quot;

Before you do coercion, you should test whether the thing you are coercing is actually what you expect it to be. So add a test to the top of the function:
stopifnot(is.formula(Arg1))

We still should separate removing the intercept from the rest of the formula. Because we don't want to see L(diff(-1,-k:k)). So we test whether -1 is in the RHS, remove it if it is and at the end, add it back if necessary:
  removeIntercept &lt;- grepl(&quot;-1&quot;, RHS)
  if(removeIntercept){
    splitRHS[[1]] &lt;- splitRHS[[1]][!grepl(&quot;-1&quot;, splitRHS[[1]])]
  }

Putting it back:
  if(removeIntercept){
    ff_RHS &lt;- paste(&quot;-1 +&quot;, ff_RHS)
  }

If we run with the suggestion in part 2, about combining the formulae, we can drop Arg2. We then split the RHS on |. Because we coerced the formula to character, we can do this with strsplit. We then want to split the formula for each of the terms. Again with strsplit.
The argument data is not used, so we can omit it.
Right now we are returning a string, as per the function in OP, but the function is called buildFormula, so it would be nice to wrap the output in as.formula, so that the name fits the output (or you can rename the function).
The new function would be:
buildFormula &lt;- function(formula){
  stopifnot(is.formula(formula))
  
  ffAsChar &lt;- as.character(formula)
  LHS &lt;- ffAsChar[2]
  RHS &lt;- strsplit(ffAsChar[3], &quot;[|]&quot;)[[1]]
  
  splitRHS &lt;- strsplit(RHS, &quot;[+]&quot;)
  removeIntercept &lt;- grepl(&quot;-1&quot;, RHS[1])
  if(removeIntercept){
    splitRHS[[1]] &lt;- splitRHS[[1]][!grepl(&quot;-1&quot;, splitRHS[[1]])]
  }
  
  
  ff_RHS &lt;- paste(splitRHS[[1]], 
                  paste0(&quot;L(diff(&quot;, 
                         splitRHS[[1]], 
                         &quot;),-k:k)&quot;), 
                  collapse = &quot; + &quot;, 
                  sep = &quot; + &quot;)
  if(length(splitRHS) &gt; 1){
    ff_RHS &lt;- paste0(ff_RHS, &quot;+&quot;,
                     paste(&quot;L(&quot;, splitRHS[[2]], &quot;,1:k)&quot;,
                           collapse = &quot; + &quot;)) 
  }
  if(removeIntercept){
    ff_RHS &lt;- paste(&quot;-1 +&quot;, ff_RHS)
  }
  paste(LHS, &quot;~&quot;, ff_RHS)
}

This will produce the following output:
&gt; buildFormula(Arg1 = y ~ x, data = dat)
[1] &quot;a ~ x + L(diff(x),-k:k)&quot;
&gt; 
&gt; buildFormula(Arg1 = y ~ x + u + z, data = dat)
[1] &quot;a ~ x  + L(diff(x ),-k:k) +  u  + L(diff( u ),-k:k) +  z + L(diff( z),-k:k)&quot;
&gt; 
&gt; buildFormula(Arg1 = y ~ x | u, data = dat) 
[1] &quot;a ~ x  + L(diff(x ),-k:k)+L(  u ,1:k)&quot;
&gt; 
&gt; buildFormula(Arg1 = y ~ x | u + z, data = dat) 
[1] &quot;a ~ x  + L(diff(x ),-k:k)+L(  u  ,1:k) + L(  z ,1:k)&quot;