Let us consider our object language JavaScripty with number literals and addition from our abstract syntax tree discussion (Section 9.3.2.1). Now, let’s extend it with variable uses and binding.
What makes a language go beyond what we might consider a calculator language is adding variable uses and binding. In the following, we show variable binding const in JavaScript, let in OCaml, and val in Scala. We have intentionally aligned them so that their syntactic differences are superficial (i.e., essentially keywords that introduce binding).
defined traitExpr
defined classN
defined classPlus
defined classVar
defined classConstDecl
As we discuss in Section 4.2 regarding scoping in Scala, the scope of a variable binding is the part of the program where that variable can be used and refers to that particular binding. Static scoping is where the scope of a variable can be determined by looking directly at the source code.
We have structured our abstract syntax so that the scope of a variable binding is apparent. In particular, the ConstDecl(\(x\),\(e_1\),\(e_2\)) is the AST node that represents binding a JavaScripty variable \(x\) (whose name is stored in Scala as x:String) to the JavaScripty value obtained by evaluating expression \(e_1\) and whose scope is exactly the JavaScripty expression \(e_2\). Note that in particular, \(x\) is not in scope in \(e_1\).
14.2 Free Variables
We can thus define a function to compute the free variables of a JavaScripty expression (represented in Scala) as follows:
deffreeVars(e: Expr):Set[Var]= e match{caseN(_)=>Set.emptycasePlus(e1, e2)=>freeVars(e1) union freeVars(e2)case x @ Var(_)=>Set(x)caseConstDecl(x, e1, e2)=>freeVars(e1)union(freeVars(e2)-Var(x))}
defined functionfreeVars
For the N(_) case, there are no free-variable uses. For the Plus(e1, e2) case, the Plus node does that change the set of free variables, so it is union of the free variables of e1 and e2.
The x @ Var(_) case is a free-variable use, so the singleton set Set(x) is the set of free variables. The @ pattern in Scala enables binding a variable and matching a specific pattern.
The ConstDecl(x, e1, e2) case shows that it is a binding construct where the variable named by x is not in scope in e1 but is in scope in e2. Uses of the variable named by x in e2 thus must be removed, as they are no longer free outside of this ConstDecl expression.
To see freeVars in action, let us consider a JavaScripty expression in concrete syntax:
const four = (2+2); (four + four)
We can represent the above JavaScripty expression as an abstract syntax tree in Scala as follows, and let us bind Scala variables to all sub-expressions:
Note it is simply one software engineering decision for the freeVars function here to have type Expr =>Set[Var], that is, it returns a set of values with data-class type Var. Another possible choice is Expr =>Set[String], which instead returns a set of the strings within the Var uses in the given Expr. The former does convey a more specific type constraint; however, the latter has the same information. A good exercise is to rewrite freeVars to have type Expr =>Set[String] to see the difference.
Exercise 14.1 Rewrite the freeVars function above to have the following type:
deffreeVarsAlt(e: Expr):Set[String]=???
defined functionfreeVarsAlt
14.3 Value Environments and Evaluation
As we discuss in Section 4.1.1 regarding value bindings in Scala, the meaning of an expression depends on the meaning of the free variables of an expression. One way to give meaning to free-variable uses is by referencing an environment that specifies the assumed meaning of each variable.
Let us consider a value environment for JavaScripty represented in Scala as a Map[Var,Double]:
type Env =Map[Var,Double]
defined typeEnv
Note that like with freeVars above in Section 14.2, it would also be reasonable to choose type Env =Map[String,Double] for the value environment mapping the variables names to their values.
As compared to the eval for number literals and addition in Section 9.3.2.1, our eval also takes a value environment env:
The x @ Var(_) case looks up the variable in the value environment env, while the ConstDecl(x, e1, e2) case extends the environment with a binding for evaluating e2.
Let us a define a “public-facing interface” function that calls eval with an empty environment (with some informational logging):
defevalExpr(e: Expr):Double={print(s"$e ⇓ ")val v =eval(Map.empty, e)println(s"$v") v}
defined functionevalExpr
It works fine for number literals and addition:
val v_n =evalExpr(e_n)val v_plusnn =evalExpr(e_plusnn)
N(2.0) ⇓ 2.0
Plus(N(2.0),N(2.0)) ⇓ 4.0
v_n: Double = 2.0v_plusnn: Double = 4.0
However, it fails unexpectedly for any expression with a free-variable use
Even though these expressions are different in the concrete syntax and quite different for a human user, they are effectively the same for a language implementation. They certainly have the same meaning according to the evalExpr function defined above in Section 14.3.
Just like with the “grouping” structure as in Section 12.1 above, we want to make evident the binding structure of an expression. Therefore, we generally consider terms equivalent up to the renaming of bound variables (e.g., we “see” the three expressions given above as the “same” expression).
Renaming bound variables consistently is also called \(\alpha\)-renaming (alpha-renaming) for historical reasons from the \(\lambda\)-calculus (lambda-calculus). Similarly, this equivalence relation on expressions is also called \(\alpha\)-equivalence.
14.4.1 Higher-Order Abstract Syntax
One way to encode the binding structure into the abstract syntax representation is encode the binding structure of the object language using the variable binding in the meta language:
Observe that there is no Expr AST node for variable uses in the object language, and instead they are represented by variable uses in the meta language in the ConstDecl AST node. This representation is called higher-order abstract syntax.
14.5 JavaScripty: Concrete Syntax: Declarations
Note that the JavaScripty grammar with \(\mathbf{const}\) above (Equation 14.1) specifies the abstract syntax using notation borrowed from the concrete syntax. The actual concrete syntax of JavaScripty is less flexible than this abstract syntax to match the syntactic structure of JavaScript. For example,
is an abstract syntax tree that would never be produced by the parser. That is,
1+const a =2; a
results in a parse error.
The JavaScripty grammar with \(\mathbf{const}\) above (Equation 14.1) read as concrete syntax is ambiguous in multiple ways, including the relative precedence of \(\mathbf{const}\)-bindings and \(\texttt{+}\)-expressions:
const b =3; b +4
JavaScript uses additional syntactic categories for “declarations” and “statements” layered on top of expressions. Variable bindings with \(\mathbf{const}\) are declarations (and not expressions).
Thus, we give a more restrictive grammar for JavaScripty with declarations and statements matching the syntactic structure of JavaScript as follows:
A declaration can be a \(\mathbf{const}\) binding (with a trailing \(\texttt{;}\)), a statement \(s\), or a sequence of declarations (i.e., \(d_1\;d_2 \mid\varepsilon\) where we consider sequencing declarations right associative). A statement can be an expression \(e\) (with a trailing \(\texttt{;}\)), a block \(\texttt{\{}\;d\;\texttt{\}}\), or an empty statement \(\texttt{;}\). Note that JavaScript parsers (like Scala’s) have some rules for semi-colon \(\texttt{;}\) inference to be a bit more flexible than this grammar. In the concrete syntax, expressions \(e\) can be parenthesized \(\texttt{(}e\texttt{)}\) and otherwise are value literals, n-ary expressions, etc.
To make JavaScripty variable declarations simpler, we also deviate slightly with respect to static scoping rules. Whereas JavaScript (like Scala) considers all bindings to be in the same scope in the same declaration list \(d\), our JavaScripty ConstDecl bindings each introduce their own scope. Essentially, we consider \(\mathbf{const}\;x\;\texttt{=}\;e_1\texttt{;}\;d_2\) in JavaScripty as \(\mathbf{const}\;x\;\texttt{=}\;e_1\texttt{;}\;\texttt{\{}\;d_2\;\texttt{\}}\) in JavaScript.