Implementation of interpreters
First we introduce some details about the implementation of interpreters, so we present information regarding the meta-programming facilities of Curry (Hanus, 2011).
Meta-programming in Curry. The implementation of our partial evaluator and our Curry interpreter relies on the meta-programming facilities of the language Curry. In particular, we consider the intermediate language FlatCurry for representing functional logic programs. In FlatCurry, all functions are defined at the top level (i.e., local function declarations in source programs are globalized by lambda lifting) and the pattern matching strategy is made explicit by the use of case expressions. In this setting, a FlatCurry program is represented by means of the following data type:
data Prog = Prog String --name of module
[String] --imported modules
[TypeDecl] --type declarations
[FuncDecl] --function declarations
[OpDecl] --operator declarations
For simplicity, here we only show the data types for representing function declarations[FuncDecl]:
data FuncDecl = Func QName --qualified name
Int --arity
Visibility --public/private
TypeExpr --type
Rule --rule
data Rule = Rule [VarIndex] Expr
Therefore, each function is represented by a single Rule whose left-hand side contains different variables ([VarIndex]) and whose right-hand side is an expression (Expr) containing variables, literals, function and constructor calls, disjunctions, and case expressions:
data Expr = Var VarIndex
| Lit Literal
| Comb CombType QName [Expr]
| Or Expr Expr
| Case CaseType Expr [BranchExpr]
data CombType = FuncCall | ConsCall
data CaseType = Rigid | Flex
data BranchExpr = Branch Pattern Expr
data Pattern = Pattern QName [VarIndex]
| LPattern Literal
Description of interpreter’s implementation. Reasoning that an interpreter is a kind of operational semantics of low-level, and therefore may serve as a definition of a programming language (Jones et al., 1993), so in Figure 7 we show the simple operational semantics which should follow our interpreters on an informal basis. However, it is really based on the basic RLNT calculus of (Albert et al., 2002) and on the syntax subset of flatCurry language (Hanus et al., 1999) shown in Section 2 of this working draft. We believe this is enough to implement our interpreters to be specialized. It is necessary to say that we do not consider some features of Curry like: higher-order neither concurrent programming for these implementations but some built-ins and constraints are.
Figure 3. Simple operational semantics for interpreters
As it can seen in the Fibonacci example, it has a main function definition, mixpo looks for the default function main to start the specialization, so if we want to specialize an interpreter w.r.t. a program it must be included into the same interpreter; we put in the right hand side of main the function call to the interpreter with the following arguments: a boolean flag, the program (to be interpreted)and the expression to be evaluated. Previously we must translate the program to FlatCurry this to include just its list of functions in flat format. Let us explain this, below, there is an example of a Curry simple interpreter; there, we have the function:
{-
int p e = do (Prog _ _ _ funs _ ) <- readFlatCurry p
print (ieval True funs e)
-}
which is enclosed by {- -}. It is only used to test the interpreter, that is the reason why it is commented. int does two actions; reads the program and prints the evaluation of expression e. The data type to represent a Curry program in the intermediate form, i.e., in FlatCurry has the constructor (Prog) and five arguments where (funs) is the set of functions. So we give just this structure (funs) into the interpreter as the program to be interpreted, ieval is the main function of the interpreter. If you see the right hand side of main function, one of ieval arguments is:
[Func ("rev2","main") 0 Public (TCons ("Prelude","Int") [ ]) (Rule [ ] (Comb FuncCall ("Prelude","+") [Comb FuncCall ("Prelude","-") [Comb FuncCall ("Prelude","*") [Lit (Intc 3), Lit (Intc 4)],Lit (Intc 1)],Lit (Intc 2)]))]
which represents the functions list in FlatCurry of the following Curry program: main = 3*4-1+2 commented also by {- -} in the example (see below). You may specialize this simple program with mixpo, able to specialize built-ins, as we hope you get the following program:
-- Program file: metaintf/examples/rev2_ann_pe
module rev2_ann(main) where
main :: a
main = 13
-- end of module metaintf/examples/rev2_ann_pe
Now, if you specialize the simple interpreter shown below, i.e., first to annotate and then specialize, you get the following program:
module int_arith_ann(main,ieval_pe1) where
import FlatCurry
main :: a
main = ieval_pe1
ieval_pe1 :: a ieval_pe1 =
FlatCurry.Comb FlatCurry.FuncCall ("Prelude","+")
[FlatCurry.Comb FlatCurry.FuncCall ("Prelude","-")
[FlatCurry.Lit (FlatCurry.Intc 12),FlatCurry.Lit (FlatCurry.Intc 1)],
FlatCurry.Lit (FlatCurry.Intc 2)]
Striking that all the code of the interpreter has been removed, but we have as a result of this partial evaluation just two function definitions: the main function and the ieval function partially evaluated.
Continuing with the implementation description of interpreters structure (which refers to the below example), we have two rules to evaluate a constructor-rooted terms, only one of them unfolds, the rule with True argument, and stops until HNF is reached. ievalList evaluates the function/constructor expression list. See the following rules:
ieval True v2 (Comb ConsCall v8 v9) = (Comb ConsCall v8 (ievalList True v2 v9) )
ieval False _ (Comb ConsCall c es) = Comb ConsCall c es
While ieval False _ (Comb ConsCall c es) is used to have some control over the evaluation of the case expression argument, i.e., to restrict the unfolding of the possible ConsCall expressions. Let us check the following segment of Curry code:
ieval top funs (Case ctype e ces) =
case (ieval False funs e) of
Comb ConsCall f es -> ieval top funs (matchBranch ces f es)
Lit l -> ieval top funs (matchBranchLit ces l)
For the sake of simplicity, we take into account only two possible cases of the resulting Case expression, both results make a call to the evaluation of the respective branch of the Case, depending on the resulting Case expression the branch is selected by the functions matchBranch or matchBranchLit.
To know the rest of description about the implementation of interpreters you may see this working draft
Continue with the specialization of interpreters
Example of a Curry simple interpreter |
module int_arith where |
Please report any bug or comment to garroyo (at) dsic.upv.es.
MiST | ELP | GPLIS | DSIC | UPV |
Last update / Gustavo Arroyo