<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Semantics in Skel and Necro</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Louis Noizet</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Alan Schmitt</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>INRIA</institution>
          ,
          <country country="FR">France</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>Université de Rennes 1</institution>
          ,
          <addr-line>Bretagne</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
      </contrib-group>
      <pub-date>
        <year>2022</year>
      </pub-date>
      <fpage>7</fpage>
      <lpage>9</lpage>
      <abstract>
        <p>We present Skel, a meta language designed to describe the semantics of programming languages, and Necro, a set of tools to manipulate said descriptions. We show how Skel, although minimal, can faithfully and formally capture informal specifications. We also show how we can use these descriptions to generate OCaml interpreters and Coq formalizations of the specified languages.</p>
      </abstract>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>To formally prove properties of a programming language, or programs in that language, it is
necessary to have a formal specification of its semantics. We expect the tool and the language
used to describe this formalization to be executable, usable, and easily verifiable, that is, close
to a paper-written specification.</p>
      <p>
        Necro provides a language (Skel) and a set of tools to formalize and interpret the semantics of
programming languages. Necro fulfills these requirements and more. First, Skel is designed to be
light and its semantics simple. A light language facilitates maintainability and the development
of tools. Second, Skel is powerful enough to express intricate semantical features, as proven by
its use in an ongoing formalization of JavaScript’s semantics [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ]. Third, a semantics described in
Skel can be close to a previously written formulation, be it as inference rules or as an algorithmic
specification. Finally, Necro provides a comprehensive and extensible set of tools to manipulate
these semantics. For instance, to translate it into an interpreter (Necro ML, see Section 3.2), or
to give a formalization in the Coq proof assistant (Necro Coq, see Section 3.3).
      </p>
      <p>
        Skel is a statically strongly typed language. First introduced in [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ], we present its redesign
with a syntax close to ML. We also introduce support for polymorphism and higher order,
enabling the use of monads in specifications [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ].
      </p>
      <p>
        Skel can be seen as a specification language or as a way to define inductive rules. Both
approaches are useful, respectively when writing a semantics whose formalization is a set of
algorithms, (e.g. ECMA-262 [
        <xref ref-type="bibr" rid="ref3">3</xref>
        ]), or when writing a semantics defined with inference rules (e.g.
 -calculus). Skel can be used to describe arbitrary semantics, including ones with partiality and
non-determinism.
      </p>
      <p>Necro contains several tools to manipulate skeletal semantics (semantics written in Skel).
First, Necro Lib ofers basic operations (parsing, typing, printing, and simple transformations),
in the form of a library to write programs that manipulate the AST describing a semantics.
Second, Necro includes the tools Necro ML, Necro Coq, and Necro Debug, which use said
library to generate respectively an OCaml interpreter, a Coq formalization, and a step-by-step
debugger.</p>
      <p>The contributions of this work are the Skel language, the accompanying tools, and many
examples of semantics available online.1</p>
      <p>The paper is organized as follows. Section 2 introduces Skel. Section 3 presents the Necro
ecosystem. Section 4 compares our work to existing approaches. Section 5 concludes the paper.</p>
    </sec>
    <sec id="sec-2">
      <title>2. Skeletal Semantics</title>
      <sec id="sec-2-1">
        <title>2.1. Skel by Example</title>
        <p>Let us describe how one specifies a semantics in Skel, using the small-step semantics of 
calculus as example. A skeletal semantics is a list of declarations, either type declarations
or term declarations, where each declaration can be unspecified or specified . When a type is
unspecified, we only give its name. For instance, when writing the semantics of  -calculus, we
might not want to specify how identifiers for variables are internally represented, so we declare
type ident.</p>
        <p>A specified type may be a variant type (i.e., an algebraic data type, defined by providing the
list of its constructors together with their input type), a type alias using the := notation, or
a record type (defined by listing its fields and their expected types). For instance, the type of
 -terms would be defined as follows.
type term = | Var ident | Lam (ident, term) | App (term, term)</p>
        <p>In this example, (ident, term) is the product type with two components, the first one being
of type ident, and the second of type term.</p>
        <p>A record type is declared as type pair = ( left: int , right: int ) and an alias as
type ident := string. Note that types are all implicitly mutually recursive, so the order in
which they are declared does not matter.</p>
        <p>We now turn to term declarations. An unspecified term declaration is simply its name and type.
To declare a specified term, we also give its definition. An example of a term we might want to let
unspecified is substitution, so we would declare val subst: ident → term → term → term.</p>
        <p>An example of a specified term is the following.
val ss (t:term): term =
match t with
| App (t1, t2) -&gt;
branch
let t1' = ss t1 in</p>
        <p>App (t1', t2)
or
let t2' = ss t2 in</p>
        <p>App (t1, t2')
or (* beta-reduction of a redex *)
1https://gitlab.inria.fr/skeletons/necro-test</p>
        <p>Term  ::=</p>
        <p>Skeleton  ::=</p>
        <sec id="sec-2-1-1">
          <title>Term decl</title>
          <p>Pattern  ::=</p>
          <p>Type  ::=
::=
::=
Type decl 
 |   | (, . . . , ) |  :  →  | . | . | ( = , . . . ,  = ) |
 ← ( = , . . . ,  = )
0 1 . . .  | let  =  in  | let  :  in  |
match  with ”|” →  . . . ”|” →  end |
branch  or . . . or  end | ret 
 | _ |   | (, . . . , ) | ( = , . . . ,  = )
 |  →  | (, . . . ,  )
val  :  | val  :  = 
type  | type  = ”|”   . . . ”|”   | type  :=  |
type  = ( : , . . . ,  :  )
let Lam (x, body) = t1 in
let Lam _ = t2 in (* t2 is a value *)
subst x t2 body (* body[x←t2] *)
end
| _ -&gt; (branch end: term)
end</p>
          <p>
            The branch . . . or . . . end construct is a Skel primitive to deal with non-deterministic
choice, similar to McCarthy’s ambiguous operator [
            <xref ref-type="bibr" rid="ref4">4</xref>
            ]. There is no order in a branching, so any
branch which yields a result can be chosen. Overlapping branches provide non-determinism,
and non-exhaustive branches provide partiality. For instance the branch end at the end will
never yield a result, so ss is partially defined.
          </p>
          <p>The destructuring pattern matching let Lam (x, body) = t1 in ... asserts that t1 is a
lambda-abstraction. If it is not, then the considered branch yields no result. If it is, then x and
body will be assigned to the proper values.</p>
          <p>The match . . . with . . . end construct is a pattern matching, similar to any other
language’s pattern matching. In particular, it is deterministic. Only the first (, )
pair for which the pattern corresponds with the matched value is taken.</p>
        </sec>
      </sec>
      <sec id="sec-2-2">
        <title>2.2. Formalism</title>
        <p>
          Figure 1 contains the syntax of Skel terms and skeletons. It does not include polymorphism,
which will be described in Section 2.5. There are two types of expressions: terms and skeletons.
Our syntax is close to Moggi’s computational  -calculus [
          <xref ref-type="bibr" rid="ref5">5</xref>
          ] and to Abstract Normal Forms [
          <xref ref-type="bibr" rid="ref6">6</xref>
          ]:
we separate what is intuitively an evaluated value to what is a computation. This makes the
evaluation order unambiguous and the manipulation of skeletal semantics simpler.
        </p>
        <p>A term is either a variable, a constructor applied to a term, a (possibly empty) tuple of terms,
a  -abstraction, the access to a given field of a term, the access to a member of a tuple, a record
of terms, or a term with reassignment of some fields. A skeleton is either the application of
Γ +  ←  ≜ Γ,  :</p>
        <p>Γ + _ ←  ≜ Γ
Γ +   ←  ≜ Γ +  ←  , where ctype() = (,  )
Γ + (1, . . . , ) ← ( 1, . . . ,  ) ≜ ((Γ + 1 ←  1) . . . ) +  ←  
 +  ←  ≜ ,  : 
 + _ ←  ≜</p>
        <p>+   ←   ≜  +  ← 
 + (1, . . . , ) ← (1, . . . , ) ≜ ( + 1 ← 1) . . . +  ← 
 + (1 = 1, . . . ,  = ) ← (1 = 1, . . . ,  = ) ≜ ( + 1 ← 1) . . . +  ← 
a term to other terms, a let-binding, an existential (see Section 2.4), a branching, a match, or
simply the return of a term. We sometimes omit ret in ret . A pattern is either a variable, a
wildcard, a constructor applied to a pattern, a (possibly empty) tuple of patterns, or a record
pattern. Finally, a type is either a base type (defined by the user), an arrow type, or a (possibly
empty) tuple of types. Term and type declarations have already been described in Section 2.1</p>
      </sec>
      <sec id="sec-2-3">
        <title>2.3. Typing</title>
        <p>Skel is a strongly typed language with explicit type annotations. Every term declaration is given
a type, as well as every pattern in a  -abstraction. Polymorphism, presented in Section 2.5, also
uses explicitly specified type arguments. Specifying every type might seem tedious at first, but
it helps to improve confidence on the correctness of the skeletal semantics. A future version of
the typer will include an optional type-inference mechanism.</p>
        <p>To give the typing rules for Skel, we first define ctype(), which returns the pair of the
declared input type and output type for the constructor  in its type declaration. For instance,
we have ctype(Var) = (ident, term). Similarly, we define ftype( ) to return (,  ) where 
is the type of the field  , and  is the record type to which the field  belongs. For instance,
we have ftype(left) = (int, pair). Note that a field name may not belong to two diferent
record types, and a constructor name may not be used twice, so these functions are well-defined.
Finally, we write fields ( ) for the fields and types of record type  .</p>
        <p>The typing rules for terms and skeletons are straightforward, we give them in Appendix A.
They are respectively of the form Γ ⊢  :  and Γ ⊢  :  , where Γ is a typing environment (a
partial function from variable names to types). To deal with pattern matching in  -abstractions
and let-bindings, we use the partial function Γ +  ←  defined at the top of Figure 2.</p>
      </sec>
      <sec id="sec-2-4">
        <title>2.4. Concrete Interpretation</title>
        <p>
          As such, Skel is a concrete syntax to describe a programming language. The meaning associated
to a Skel description is called an interpretation. We present in this section a concrete
interpretation, which stands for the usual natural semantics of a language [
          <xref ref-type="bibr" rid="ref7">7</xref>
          ]. One may also define an
abstract interpretation, where unspecified types are given values in some abstract domain and
where the results from branches are all collected. Such an interpretation is beyond the scope of
this paper.
        </p>
        <p>Every skeleton and term is interpreted as a value. Given sets of values  for each unspecified
type  , we build values for variant and product types in the expected way. For arrow types, we
use closures for the specified functions, and we delay the evaluation of unspecified terms until
we have all arguments. To this end, for each declaration val x :  1 → . . . →  n →  , we
assume given an arity  = arity() ∈ N and a relation JK ∈ ( 1 × · · · ×   ×  ).</p>
        <p>The rules for the concrete interpretation are straighforward. They are given in Appendix B.
They are of the form ,  ⇓  for terms and ,  ⇓  for skeletons, meaning that in the
environment  (partial function mapping variables to values), the term  or the skeleton  can
evaluate to a value .</p>
        <p>A construct that is specific to Skel is the existential. To interpret the existential let p: in sk,
we take any value of type  , and match p to this value, before evaluating the continuation in the
extended environment. This pattern matching, also used for the let-in constructs and closures,
is defined in Figure 2 as the partial function  +  ←  that returns an extended environment.</p>
        <p>The concrete interpretation is relational: a skeleton can be interpreted to 0, 1, or several
values. A branching with no branch has no result, causing partiality. A branching with several
branches can have several results, causing non-determinism. Pattern-matching can fail, causing
partiality. Finally, non-terminating computations also cause partiality.</p>
      </sec>
      <sec id="sec-2-5">
        <title>2.5. Polymorphism and Type Inference</title>
        <p>Our type system allows for polymorphism, with explicit type annotations specified using angle
brackets. Any declared and defined type can be polymorphic.
(* Unspecified *)
type list&lt;_&gt;
(* Record *)
type pair&lt;a,b&gt; = (left: a, right: b)
(* Variant *)
type union&lt;a,b&gt; = | InjL a | InjR b
(* Alias *)
type option&lt;a&gt; := union&lt;a,()&gt;</p>
        <p>Terms defined at toplevel can also be polymorphic, but let-bound terms are necessarily
monomorphic.
val map&lt;a,b&gt; (f: a → b) (l: list&lt;a&gt;): list&lt;b&gt; =
branch
let Nil = l in</p>
        <p>Nil&lt;b&gt;
or
end
let Cons (a, qa) = l in
let b = f a in
let qb = map&lt;a,b&gt; f qa in
Cons&lt;b&gt; (b, qb)</p>
        <p>IMP</p>
        <sec id="sec-2-5-1">
          <title>JavaScript</title>
          <p>-calculus</p>
        </sec>
        <sec id="sec-2-5-2">
          <title>User</title>
        </sec>
        <sec id="sec-2-5-3">
          <title>Skel</title>
          <p>Necro Lib → necro.cma</p>
        </sec>
        <sec id="sec-2-5-4">
          <title>Necro ML</title>
        </sec>
        <sec id="sec-2-5-5">
          <title>Necro Coq</title>
        </sec>
        <sec id="sec-2-5-6">
          <title>Necro Trans</title>
        </sec>
        <sec id="sec-2-5-7">
          <title>Necro Debug . . .</title>
        </sec>
        <sec id="sec-2-5-8">
          <title>OCaml Coq</title>
        </sec>
        <sec id="sec-2-5-9">
          <title>Debugger</title>
          <p>Type annotations are explicitly given when constructing a term (e.g., Nil&lt;b&gt;) or when using
a polymorphic term (e.g., map&lt;a,b&gt; f qa), but they can be locally inferred in patterns, so they
are not specified in them (e.g., let Nil = l in . . . ).</p>
          <p>The explicit typing is by design, as explicit type annotations reduce the risk of error. In the
future, we will add an option to perform type inference. This option could also infer the type of
the arguments in constructs of the form  p: → sk.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>3. The Necro Ecosystem</title>
      <p>Necro is an ecosystem with several tools to perform diferent operations on skeletal semantics,
as illustrated in Figure 3.</p>
      <sec id="sec-3-1">
        <title>3.1. Necro Lib</title>
        <p>
          Necro Lib [
          <xref ref-type="bibr" rid="ref8">8</xref>
          ] is an OCaml library file, necro.cma. It provides a parser and a typer, in order to
transform a Skel file into a Skel AST. The AST is described in the file main/skeltypes.mli
of the repository. It also contains a pretty-printer, which displays the AST in the format of a
Skel file; a set of transformers, along with the tool Necro Trans that calls the transformers on an
AST and prints the result; and a set of utility functions to manipulate the AST. The necro.cma
ifle is the basis for Necro ML (Section 3.2) and Necro Coq (Section 3.3).
        </p>
      </sec>
      <sec id="sec-3-2">
        <title>3.2. Necro ML</title>
        <p>
          Necro ML [
          <xref ref-type="bibr" rid="ref9">9</xref>
          ] is a generator of OCaml interpreters. Given a skeletal semantics, it produces an
OCaml functor. This functor expects as arguments an OCaml specification of all unspecified
types and terms, it then provides an interpreter that can compute any given skeleton. Skel
cannot be shallowly embedded in OCaml, since OCaml does not have an operator fitting
the branching construct (pattern matching is deterministic in OCaml). So types and terms are
shallowly embedded, but skeletons are deeply embedded. We use an interpretation monad, which
specifies, amongst other things, how skeletons are represented, and how let ins, applications
and branches are computed.
3.2.1. OCaml Interpreter
When Necro ML is executed on a skeletal semantics, it generates an OCaml file, which contains
several modules, functors, and module types. To create an interpreter, one needs to
instantiate them, in order to indicate how unspecified types and terms are interpreted, and which
interpretation monad is chosen.
        </p>
        <p>Working examples can be found in the test folder of Necro ML’s repository.
3.2.2. Interpretation Monad
The interpretation monad is defined as follows, where terms are assumed to be pure while
skeletons are monadic (of type 'a t).
module type MONAD = sig
type 'a t
val ret: 'a -&gt; 'a t
val bind: 'a t -&gt; ('a -&gt; 'b t) -&gt; 'b t
val branch: (unit -&gt; 'a t) list -&gt; 'a t
val fail: string -&gt; 'a t
val apply: ('a -&gt; 'b t) -&gt; 'a -&gt; 'b t
val extract: 'a t -&gt; 'a
end</p>
        <p>The fail operator takes a string as input which is an error message that should be raised,
and the extract operator is a usability construct to extract a result from the monad, typically
to display it.</p>
        <p>
          There are several proposed ways to instantiate this monad, and the user can also define their
own. The standard identity monad, which is closest to a shallow embedding, tries each branch
in turn. But it chooses the first branch that succeeds, with no ability to backtrack, which can be
problematic in some cases. A more interesting one is the continuation monad, which keeps as a
failure continuation the branches not taken, and can then backtrack if need be [
          <xref ref-type="bibr" rid="ref10">10</xref>
          ].
module ContPoly = struct
type 'b fcont = string -&gt; 'b
type ('a,'b) cont = 'a -&gt; 'b fcont -&gt; 'b
type 'a t = { cont: 'b. (('a,'b) cont -&gt; 'b fcont -&gt; 'b) }
let ret (x: 'a) = { cont = fun k fcont -&gt; k x fcont }
let bind (x: 'a t) (f: 'a -&gt; 'b t) : 'b t =
        </p>
        <p>{ cont = fun k fcont -&gt; x.cont (fun v fcont' -&gt; (f v).cont k fcont') fcont }
let fail s = { cont = fun k fcont -&gt; fcont s }
let rec branch l = { cont = fun k fcont -&gt;
begin match l with
| [] -&gt; fcont "No branch matches"
| b :: bs -&gt; (b ()).cont k (fun _ -&gt; (branch bs).cont k fcont)
end}
let apply f x = f x
let extract x = x.cont (fun a _ -&gt; a) (fun s -&gt; failwith s)
end</p>
        <p>Nevertheless, the continuation monad is not complete w.r.t the concrete interpretation, as
it always executes the first branch first, and can therefore be caught in an infinite loop. The
following function is an example thereof.
val loop (_:()): () = branch loop () or () end</p>
        <p>To fix this issue, we propose another interpretation monad called BFS, which does one step
in each branch, until it gets a result. The file containing all these monads and some others is
available online.2 Note that to change the interpretation monad, the user simply has to swap it
at module instantiation, no code needs to be rewritten.</p>
      </sec>
      <sec id="sec-3-3">
        <title>3.3. Necro Coq</title>
        <p>
          3.3.1. Structure
Necro Coq [
          <xref ref-type="bibr" rid="ref11">11</xref>
          ] is a tool to embed a given skeletal semantics into a Coq formalization. It can
then be used to prove language properties or correctness of a given program.
Necro Coq uses a deep embedding of Skel. The embedding of Skel is defined in the file
files/Skeleton.v, which is presented in Section 3.3.2. The command necrocoq file.sk
provides a Coq file that contains the AST of the original skeletal semantics. To give meaning to
the skeletons, we then provide a file that defines the concrete interpretation for skeletons and
terms.
3.3.2. Skel’s Embedding
The embedding is a straightforward deep embedding. It defines a number of variables, which
are the data of a given skeletal semantics (its constructors, base types, . . . ), and provides the
basic constructs (the definition of a type, a skeleton, . . . ).
3.3.3. Values
The values that are the result of the evaluation of a term or skeleton are deeply embedded as
well. The Coq type which supports values is defined with five constructors. Here are the first
four.
        </p>
        <p>Inductive cvalue : Type :=
| cval_base : forall A, A -&gt; cvalue
| cval_constructor : constr -&gt; cvalue -&gt; cvalue
| cval_tuple: list cvalue -&gt; cvalue
| cval_closure: pattern -&gt; skeleton -&gt; list (string * cvalue) -&gt; cvalue.
2https://gitlab.inria.fr/skeletons/necro-ml/-/blob/master/necromonads.ml</p>
        <p>The cval_base constructor allows values of an unspecified type to be represented in Coq
with any given type A. For instance, the value 1, in an unspecified type int, could be stored as
cval_base Z 1. The next two constructors are straightforward. The fourth one is a closure
constructor, to store  -abstractions. The three arguments of the constructor are the bound
pattern, the skeleton to evaluate, and the current environment. The fifth constructor is used for
unspecified functional terms, it will be presented below.
3.3.4. Interpretation
The file Concrete.v3 provides the concrete interpretation for skeletons. It uses Coq’s induction
to define the relations interp_skel and interp_term, which relate respectively skeletons and
terms in a given environment to their possible interpretations as a value. It mostly follows the
semantics of Appendix B. For instance, this is the rule for a let in construct:
Inductive interp_skel: env -&gt; skeleton -&gt; cvalue -&gt; Prop :=
| i_letin: forall e e' p s1 s2 v w,
interp_skel e s1 v -&gt;
add_asn e p v e' -&gt;
interp_skel e' s2 w -&gt;
interp_skel e (skel_letin p s1 s2) w</p>
        <p>The add_asn e p v e' proposition states that the environment  can be extended into ′ by
mapping  to , that is ′ =  +  ←</p>
        <p>
          The file Concrete.v defines the interpretation using big-step evaluation, but we also provide
a file Concrete_ss.v which does a small-step evaluation. An alternative is ConcreteRec.v,
which defines interpretation from the bottom up. That is, instead of using Coq’s induction, it
only defines how to do one step, which doesn’t use recursive calls, and then one may iterate this
step. It is closest to the initial definition in [
          <xref ref-type="bibr" rid="ref2">2</xref>
          ]. The purpose of this file is to be able to perform a
strong induction on interp_skel in a simple way.
        </p>
        <p>These interpretations are proven (in Coq) to be equivalent, so one can use indiferently one
or the other, and one may even switch between several of them depending on what is more
useful at the time. The big step definition is usually the easiest to use. The small step one allows
to reason about non-terminating behaviors, and it provides a simple way to prove the subject
reduction of Skel (see below). The iterative one allows, as we mentioned, to perform a strong
induction. An instance where we need to use this one is to prove an induction property on
the semantics of a lambda calculus.4 As Skel is deeply embedded, one evaluation step in the
language corresponds to several steps in Necro Coq. Because of this, the inductive interpretation
is not convenient to prove that property, whereas this is much simpler using the iterative version
with a strong induction on the height of the derivation tree.</p>
        <p>As Skel is strongly typed, we also have a file WellFormed.v to check that a term or skeleton
is well-formed, i.e., that it can be typed. We prove that the concrete interpretation respects the
subject reduction property with regards to well-formedness. Since interpretations are all shown
to be equivalent, it sufices to only prove it for Concrete_ss.v:
3https://gitlab.inria.fr/skeletons/necro-coq/-/blob/master/files/Concrete.v
4https://gitlab.inria.fr/skeletons/necro-coq/-/blob/master/test/lambda/EvalInd.v#L55
Theorem subject_reduction_skel:
forall sk sk' ty,
type_ext_skel sk ty -&gt;
interp_skel_ss sk sk' -&gt;
type_ext_skel sk' ty.</p>
        <p>This translates roughly to:
 :</p>
        <p>→ ′
′ : 
where S and S’ are extended skeletons.</p>
        <p>With this proven, and since Concrete_ss.v and Concrete.v are equivalent, we have:
∅ ⊢  :</p>
        <p>⇓ 
 ∈</p>
        <p>
          Finally, interpretations in the form of abstract machines have been defined in [
          <xref ref-type="bibr" rid="ref12">12</xref>
          ]. They
reuse the deep embedding provided by Necro Coq and are proven correct in relation to the
concrete interpretation.
3.3.5. Unspecified Functional Values
Since Skel allows to declare unspecified terms, we must be able to interpret them. The natural
idea would be to ask for a cvalue for each given unspecified term. But for unspecified functions
(like the addition), that would mean giving a closure, which is equivalent to specifying the
function. We would lose Skel’s power of partial specification. Instead, we ask for a relation that,
given a list of cvalues as arguments, provides the result of the application of the term to the
arguments. For instance, for the addition, it would be {([; ],  + ) | ,  ∈ N}.
        </p>
        <p>There are operational and denotational approaches to represent this in Coq. We choose the
operation approach as it does not need to evaluate ahead of time: it just waits to be applied to
enough arguments before computing. We thus use the following constructor, which denotes an
unspecified functional term that is not fully applied yet.
| cval_unspec: nat -&gt; unspec_value -&gt; list type -&gt; list cvalue -&gt; cvalue.</p>
        <p>
          The first nat argument being n means that there are S n arguments missing. We add one,
because there cannot be 0 argument missing, since if there is 0 argument missing, it is not a
partial application. The list of types is the type annotation for polymorphic unspecified terms,
the list of values is the list of arguments that have already been provided.
3.3.6. Applications and Usability
We have considered several applications of Necro Coq, some of them can be found in the test
folder of the repository. For instance, we have proved the correctness of an IMP code that
computes the factorial function (test/certif folder). Necro Coq has also been used to prove
the equivalence between a small-step and a big-step semantics for a  -calculus extended with
natural numbers (test/lambda folder). In addition, it has been used in [
          <xref ref-type="bibr" rid="ref13">13</xref>
          ] to provide the a
posteriori proof of an automatic generic transformation of a big step semantics into a small step
semantics.
        </p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>4. Related Work</title>
      <p>We review existing approaches that are generic, in the sense that they can be used to describe
and manipulate any semantics.</p>
      <p>
        Our work is an extension of the work undertaken in [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ]. We significantly improve on this
approach by having a more expressive language (with higher-order functions and polymorphism)
and a set of tools to manipulate skeletal semantics. The Coq formalizations of [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ] were written by
hand, they can now be automatically generated. Generation of OCaml code was also proposed
in [
        <xref ref-type="bibr" rid="ref14">14</xref>
        ], but it was only available for the language of [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ], which was less powerful than the
current language. It also did not consider interpretation monads.
      </p>
      <p>
        Regarding meta-languages to describe semantics, existing tools are much more complex than
Skel. This is the case of Lem [
        <xref ref-type="bibr" rid="ref15">15</xref>
        ] and Ott [
        <xref ref-type="bibr" rid="ref16">16</xref>
        ]. The simplicity of Skel (the file skeltypes.mli
describing Skel’s AST is only 114 lines of specification) allows anyone to easily write a tool
handling skeletal semantics. This is less immediate with Lem and Ott, as one has to deal with
many additional features. For instance, Lem natively defines set comprehension, relations, and
maps. Also, Coq generation is done as a shallow embedding, hence functions must be proven
to terminate. In addition, shallow embedding of large semantics are not easily manipulated in
Coq, due to the space complexity of the induction and inversion tactics.
      </p>
      <p>The K framework [17] also allows to formally define the semantics of a language and prove
programs, and it is designed to be easy to use. It does not allow, however, to prove meta-theory
properties of a language5, which is one of our future goals. Furthermore, there are no Coq
backend for K at the present time, and since K is a large language, writing new backends is far
from trivial.</p>
      <p>Finally, another common way to describe a semantics is to implement it both in OCaml and in
Coq, or other similar tools, either directly or through tools to transform them (Coq extraction to
OCaml or coq-of-ocaml [18] to go the other way). One may then execute the semantics using
the OCaml version and prove properties using the Coq one. In this case, the Coq formalization
is simpler to manipulate, but changing design choices (such as going from a shallow to a deep
embedding) is very costly, as OCaml or Coq AST are not easy to manipulate.</p>
    </sec>
    <sec id="sec-5">
      <title>5. Conclusion</title>
      <p>Skel ofers a way to specify semantics of programming languages, using a language light enough
to be easily readable and maintainable, yet powerful enough to express many semantical features.
We have focused on dynamic semantics, but one may also describe static semantics in Skel.
The tools Necro provide, such as Necro ML, help in the process of writing a semantics. Necro
Coq allows to manipulate and certify these semantics once written. They give the necessary
framework to prove program correctness and language properties.</p>
      <p>
        Skel has been used to write the semantics of a set of basic languages such as IMP, but it has
also been used to formalize more massive languages, such as WASM (unpublished), and an
ongoing formalization of JavaScript [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ]. We plan to write a formalization of Python based on an
existing precise description [19].
      </p>
      <p>The Necro ecosystem includes other tools, such as Necro Debug,6 which is a step-by-step
execution of a semantics. An example execution can be found online.7. In addition, people can
easily produce a new tool using Necro Lib.</p>
      <p>Although not mentioned in this paper, Skel allows to split the definition of a semantics in
several files, and to access them using an include construct. The OCaml generation tool can
handle these multi-file semantics using modules, but at the moment this is not the case for
Necro Coq. A future task is to implement this functionality using Coq modules. Once this has
been done, the logical next step is to implement a standard library for Skel, defining basic types
like lists, with properties on these types proven using Necro Coq. Initial work on this standard
library can be found online.8</p>
      <p>
        Finally, Skel and Necro are currently being used to describe semantics style transformations,
both at the object level [
        <xref ref-type="bibr" rid="ref13">13</xref>
        ] and at the meta-language level [
        <xref ref-type="bibr" rid="ref12">12</xref>
        ]. Work has also started to
automatically derive control flow analyses from a language description.
6https://gitlab.inria.fr/skeletons/necro-debug
7https://skeletons.inria.fr/debugger/index_while.html
8https://gitlab.inria.fr/skeletons/necro/-/tree/master/examples/necro_in_necro
Efective tool support for the working semanticist, J. Funct. Program. 20 (2010) 71–122.
doi:10.1017/S0956796809990293.
[17] L. Li, E. L. Gunter, IsaK: A Complete Semantics of K, Technical Report, Computer Science,
      </p>
      <p>Univ. of Illinois Urbana-Champaign, 2018.
[18] N. Labs, coq-of-ocaml, https://clarus.github.io/coq-of-ocaml, ???? URL: https://clarus.</p>
      <p>github.io/coq-of-ocaml.
[19] R. Monat, Static Type and Value Analysis by Abstract Interpretation of Python Programs
with Native C Libraries, Ph.D. thesis, Sorbonne Université, 2021.</p>
    </sec>
    <sec id="sec-6">
      <title>A. Typing Rules for Skeletal Semantics</title>
      <p>Γ() = 
Γ ⊢  :</p>
      <p>Var
val x:
Γ ⊢  :</p>
      <p>TermUnspec
val x: = t
Γ ⊢  :</p>
      <p>TermSpec
Γ ⊢  :</p>
      <p>ctype(C) = (,  ′)
Γ ⊢ C  :  ′</p>
      <p>Const
Γ ⊢ 1 :  1 . . . Γ ⊢  :   Tuple
Γ ⊢ (1, . . . , ) : ( 1, . . . ,  )
Γ +  ←  ⊢  :  ′
Γ ⊢ ( :  → ) :  →  ′</p>
      <p>Clos
Γ ⊢  :</p>
      <p>ftype( ) = (,  )
Γ ⊢ . :</p>
      <p>FieldGet
Γ ⊢  : ( 1, . . . ,  )
Γ ⊢ . :</p>
      <p>∀ ∈Γ J⊢1; ← K, Γ(⊢1 = :1, . . . , ∀ =∈ J1;):K, ftype  = ( ,  ) FieldSet
Γ ⊢  : 
Γ ⊢ ret  :</p>
      <p>Ret
Γ ⊢ 1 :  . . . Γ ⊢  :  Branch
Γ ⊢ (1 . . . ) : 
Γ ⊢  :  Γ +  ←  ⊢ ′ :  ′
Γ ⊢ let  =  in′ :  ′</p>
      <p>LetIn
ΓΓ⊢+ le← t  : ⊢in::  ′′ Exist
Γ ⊢  : 
Γ ⊢ 0 :  1 → · · · →
Γ + 1 ←  ⊢ 1 :  . . . Γ +  ←  ⊢  : 
Γ ⊢ match  with|1 → 1|. . . | →  : 
  →  Γ ⊢ 1 :  1
Γ ⊢ (0 1 . . . ) : 
. . .</p>
      <p>Γ ⊢  :</p>
      <p>Match</p>
      <p>App</p>
    </sec>
    <sec id="sec-7">
      <title>B. Concrete Interpretation of Skeletal Semantics</title>
      <p>First we defined rules to handle environments. We define the relation  ̸↦→  which holds if 
cannot represent the value , and the partial function  +  match(1, . . . , ) as follows:
 ̸= ′
 ̸↦→ ′</p>
      <p>NoMatchTuple</p>
      <p>NoMatchSameConstr</p>
      <p>NoMatchDiffConstr
 ̸↦→</p>
      <p>0 ≤  ≤ 
(1, . . . , ) ̸↦→ (1, . . . , )
 ̸↦→</p>
      <p>0 ≤  ≤  ≤ 
(1 = 1, . . . ,  = ) ̸↦→ (1 = 1, . . . ,  = )</p>
      <p>NoMatchRecord
 + 1 ←  = ′
 +  match(1, . . . , ) = (1, ′)</p>
      <p>GetMatchFirst
1 ̸↦→ 
 +  match(1, . . . , ) = ( + 1, ′)
 +  match(2, . . . , ) = (, ′)</p>
      <p>GetMatchNext
Now we define the rules for concrete interpretation:
(,)⇓=  Var val x :  ar,ity⇓() = 0
arity() =  ≤  JK(1, . . . , , )  +1 . . .  ⇓app  AppUnspecCont
⌈, (1, . . . , )⌉ +1 . . .  ⇓app</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>A.</given-names>
            <surname>Khayam</surname>
          </string-name>
          ,
          <string-name>
            <given-names>L.</given-names>
            <surname>Noizet</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Schmitt</surname>
          </string-name>
          , Jskel:
          <article-title>Towards a formalization of javascript's semantics</article-title>
          ,
          <source>in: JLFA 2021 - Journées Francophones des Langages Applicatifs</source>
          ,
          <year>2021</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>M.</given-names>
            <surname>Bodin</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Gardner</surname>
          </string-name>
          ,
          <string-name>
            <given-names>T.</given-names>
            <surname>Jensen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Schmitt</surname>
          </string-name>
          ,
          <source>Skeletal Semantics and their Interpretations</source>
          ,
          <source>Proceedings of the ACM on Programming Languages</source>
          <volume>44</volume>
          (
          <year>2019</year>
          )
          <fpage>1</fpage>
          -
          <lpage>31</lpage>
          . URL: https://hal.inria. fr/hal-01881863. doi:
          <volume>10</volume>
          .1145/3290357.
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <issue>TC39</issue>
          , ECMA-
          <volume>262</volume>
          , https://262.ecma-international.org/, ???? URL: https: //262.ecma-international.org/.
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>J.</given-names>
            <surname>McCarthy</surname>
          </string-name>
          ,
          <article-title>A basis for a mathematical theory of computation</article-title>
          , in: P.
          <string-name>
            <surname>Brafort</surname>
          </string-name>
          , D. Hirschberg (Eds.),
          <source>Computer Programming and Formal Systems</source>
          , volume
          <volume>26</volume>
          <source>of Studies in Logic and the Foundations of Mathematics, Elsevier</source>
          ,
          <year>1959</year>
          , pp.
          <fpage>33</fpage>
          -
          <lpage>70</lpage>
          . URL: https:// www.sciencedirect.com/science/article/pii/S0049237X09700990. doi:https://doi.org/ 10.1016/
          <fpage>S0049</fpage>
          -237X(
          <issue>09</issue>
          )
          <fpage>70099</fpage>
          -
          <lpage>0</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>E.</given-names>
            <surname>Moggi</surname>
          </string-name>
          ,
          <article-title>Computational lambda-calculus and monads</article-title>
          ,
          <source>Proceedings. Fourth Annual Symposium on Logic in Computer Science</source>
          (
          <year>1988</year>
          ).
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>A.</given-names>
            <surname>Sabry</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Felleisen</surname>
          </string-name>
          ,
          <article-title>Reasoning about programs in continuation-passing style</article-title>
          ,
          <source>in: LISP AND SYMBOLIC COMPUTATION</source>
          ,
          <year>1993</year>
          , pp.
          <fpage>288</fpage>
          -
          <lpage>298</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>G.</given-names>
            <surname>Kahn</surname>
          </string-name>
          ,
          <article-title>Natural semantics</article-title>
          , in: F. Brandenburg,
          <string-name>
            <given-names>G.</given-names>
            <surname>Vidal-Naquet</surname>
          </string-name>
          ,
          <string-name>
            <surname>M.</surname>
          </string-name>
          Wirsing (Eds.),
          <source>STACS 87, 4th Annual Symposium on Theoretical Aspects of Computer Science</source>
          , Passau, Germany,
          <source>February 19-21</source>
          ,
          <year>1987</year>
          , Proceedings, volume
          <volume>247</volume>
          of Lecture Notes in Computer Science, Springer,
          <year>1987</year>
          , pp.
          <fpage>22</fpage>
          -
          <lpage>39</lpage>
          . URL: https://doi.org/10.1007/BFb0039592. doi:
          <volume>10</volume>
          .1007/ BFb0039592.
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>L.</given-names>
            <surname>Noizet</surname>
          </string-name>
          , Necro Library, https://gitlab.inria.fr/skeletons/necro, ???? URL: https://gitlab. inria.fr/skeletons/necro.
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <given-names>E. C.</given-names>
            <surname>Martin Bodin</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Nathanaëlle</given-names>
            <surname>Courant</surname>
          </string-name>
          ,
          <string-name>
            <given-names>L.</given-names>
            <surname>Noizet</surname>
          </string-name>
          , Necro Ocaml Generator, https://gitlab.inria.fr/skeletons/necro-ml, ???? URL: https://gitlab.inria.fr/skeletons/ necro-ml.
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <given-names>O.</given-names>
            <surname>Danvy</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Filinski</surname>
          </string-name>
          , Abstracting control,
          <source>in: Proceedings of the 1990 ACM Conference on LISP and Functional Programming</source>
          ,
          <source>LFP '90</source>
          ,
          <string-name>
            <surname>Association</surname>
          </string-name>
          for Computing Machinery, New York, NY, USA,
          <year>1990</year>
          , p.
          <fpage>151</fpage>
          -
          <lpage>160</lpage>
          . URL: https://doi.org/10.1145/91556.91622. doi:
          <volume>10</volume>
          . 1145/91556.91622.
        </mixed-citation>
      </ref>
      <ref id="ref11">
        <mixed-citation>
          [11]
          <string-name>
            <given-names>L.</given-names>
            <surname>Noizet</surname>
          </string-name>
          , Necro Gallina Generator, https://gitlab.inria.fr/skeletons/necro-coq, ???? URL: https://gitlab.inria.fr/skeletons/necro-coq.
        </mixed-citation>
      </ref>
      <ref id="ref12">
        <mixed-citation>
          [12]
          <string-name>
            <given-names>G.</given-names>
            <surname>Ambal</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Lenglet</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Schmitt</surname>
          </string-name>
          ,
          <article-title>Certified Abstract Machines for Skeletal Semantics</article-title>
          , in: Certified Programs and Proofs, Philadelphia, United States,
          <year>2022</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref13">
        <mixed-citation>
          [13]
          <string-name>
            <given-names>G.</given-names>
            <surname>Ambal</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Schmitt</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Lenglet</surname>
          </string-name>
          ,
          <article-title>Automatic Transformation of a Big-Step Skeletal Semantics into Small-Step</article-title>
          ,
          <source>Research Report RR-9363</source>
          , Inria Rennes - Bretagne
          <string-name>
            <surname>Atlantique</surname>
          </string-name>
          ,
          <year>2020</year>
          . URL: https://hal.inria.fr/hal-02946930.
        </mixed-citation>
      </ref>
      <ref id="ref14">
        <mixed-citation>
          [14]
          <string-name>
            <given-names>N.</given-names>
            <surname>Courant</surname>
          </string-name>
          ,
          <string-name>
            <given-names>E.</given-names>
            <surname>Crance</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Schmitt</surname>
          </string-name>
          , Necro: Animating Skeletons,
          <source>in: ML</source>
          <year>2019</year>
          , Berlin, Germany,
          <year>2019</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref15">
        <mixed-citation>
          [15]
          <string-name>
            <given-names>D.</given-names>
            <surname>Mulligan</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Owens</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Gray</surname>
          </string-name>
          ,
          <string-name>
            <given-names>T.</given-names>
            <surname>Ridge</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Sewell</surname>
          </string-name>
          , Lem:
          <article-title>Reusable engineering of real-world semantics</article-title>
          ,
          <source>ACM SIGPLAN Notices</source>
          <volume>49</volume>
          (
          <year>2014</year>
          ). doi:
          <volume>10</volume>
          .1145/2628136.2628143.
        </mixed-citation>
      </ref>
      <ref id="ref16">
        <mixed-citation>
          [16]
          <string-name>
            <given-names>P.</given-names>
            <surname>Sewell</surname>
          </string-name>
          ,
          <string-name>
            <given-names>F.</given-names>
            <surname>Zappa Nardelli</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Owens</surname>
          </string-name>
          , G. Peskine,
          <string-name>
            <given-names>T.</given-names>
            <surname>Ridge</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Sarkar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R.</given-names>
            <surname>Strnisa</surname>
          </string-name>
          , Ott:
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>