<!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>Interactive Debugging for Extensible Languages in Multi-Stage Transformation Environments</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Domenik Pavletic</string-name>
          <email>pavletic@itemis.com</email>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Kim Haßlbauer</string-name>
          <email>kim.hasslbauer@gmail.com</email>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Stuttgart</institution>
          ,
          <country country="DE">Germany</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>itemis AG</institution>
          ,
          <addr-line>Stuttgart</addr-line>
          ,
          <country country="DE">Germany</country>
        </aff>
      </contrib-group>
      <fpage>19</fpage>
      <lpage>25</lpage>
      <abstract>
        <p>-Extensible languages have a base language that can be extended incrementally with new language extensions, forming a stack with high-level languages on top and lower level languages at the bottom. Programs written with these languages are usually a mixture of code using base language and several language extensions. These extensions come with generators that translate higher level language constructs to lower levels and ultimately to base language. Program bugs appearing at runtime can be introduced on the source level by language users or through faulty transformation rules by language engineers. The latter category of bugs are often analyzed with a base language debugger, because language constructs introducing the bug on intermediate levels usually have no representation on the source level. However, due to the semantic gap between generated code and the intermediate program where a bug is introduced, users have to map between abstraction levels manually, which is error prone and requires additional effort besides analyzing the bug. In this paper we present an approach to build multi-level debuggers for extensible languages that allow language users to debug their code on the source level and language engineers to debug on intermediate levels created during code generation. We illustrate this approach with an implementation for the MPS language workbench and mbeddr C, an extensible C language. Index Terms-Formal languages, Software debugging.</p>
      </abstract>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>I. INTRODUCTION</title>
      <p>Extensible languages are used to develop software systems
on a higher level of abstraction and consist of a base language,
usually a General Purpose Language (GPL), which can be
extended with new, usually domain-specific, language
extensions on the syntactical and semantic level. Hence, extensible
language programs comprise different languages, residing on
different abstraction levels. Each of these extensions comes
with a generator that translates code to a more concrete
language. This translation happens incrementally from higher
levels to lower ones, until we get a pure base language
program. Fig. 1 shows the process from transforming the
source-level Abstract Syntax Graph (ASG) of an extensible
language program to the base level via Model 2 Model
(M2M) transformations and ultimately to text (target level) via
Model 2 Text (M2T) transformations. Graphs inside the boxes
represent ASGs of the respective abstraction level, colors
indicate structural modifications.</p>
      <p>
        The effort for building such extensible languages can be
reduced by using a language workbench, an Integrated
Development Environment (IDE) for language engineering. These
tools provide facilities to implement language definitions
and usually come with generator frameworks to build
multistage transformations. JetBrains Meta Programming System
(MPS) [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ] is a workbench that supports the development of
extensible languages, mbeddr [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ] is one of these languages
built with MPS. This language is based on C and comes with a
set of language extensions for embedded software engineering.
      </p>
      <p>
        Runtime bugs that appear in extensible language programs
during execution can either be introduced on the
sourcelevel by language users or on intermediate levels (e. g.,
level 1.0 in the figure above) by language engineers through
transformation rules. While language users require a
sourcelevel debugger to analyze bugs introduced by themselves,
language engineers often investigate bugs introduced through
faulty transformation rules by debugging the generated code
or the transformation process. Analyzing the transformation
process, e. g., by using an omniscient debugger [
        <xref ref-type="bibr" rid="ref3">3</xref>
        ], allows
language engineers to identify the rule that introduced a faulty
code segment and why this rule was executed, however, this
approach does not support analyzing the runtime behavior of
this generated code segment. Further, generators may have
modified the program structure, e. g., by changing identifiers
or the structure of statements. This abstraction mismatch
makes it hard to debug the execution of the generated code.
      </p>
      <p>We present in this paper an approach to build
multilevel debuggers for extensible languages. These debuggers
enable language engineers to debug programs on different
levels, thus allowing them to analyze the runtime behavior of
code generated from faulty transformation rules. Further, and
essential, these debuggers imitate stepping behavior and show
program state based on the languages used on the investigated
abstraction level, which can be an intermediate level that has
been generated by a faulty transformation. This approach also
enables source-level debugging targeting language users.</p>
    </sec>
    <sec id="sec-2">
      <title>II. DEBUGGING EXTENSIBLE LANGUAGES</title>
      <p>Interactive debuggers allow users to inspect and animate the
execution of a program. These debuggers usually operate on
the source level and provide, depending on the actual tool,
different functionalities: users can put breakpoints on source
lines or memory addresses. Further, they can use stepping
commands to animate the execution and inspect the program
state. Some debuggers even allow users to manipulate values
of watch variables or interpret arbitrary expressions.</p>
      <p>Programming languages usually support source-level
debugging, as multi-level debugging makes little sense in this
context: first, these languages are usually not extensible and
get transformed to the target language, via a single
intermediate language. Debugging intermediate levels is not useful to
language users or compiler engineers, as this code is usually
similar to the target level representation. Second, language
users are often not familiar with the underlying target language
(e. g., assembler). They rather consider the compiler as a
black box that is configurable via parameters. With extensible
languages, the situation is different: while extensions have a
common base language, they can be stacked in an arbitrary
hierarchical way. That is, new language extensions can be
hooked into the generation process. This flexibility increases
the complexity and the possibility for introducing bugs into
the program through faulty transformations. As described at
the end of the section above, bugs introduced by language
users can be analyzed with a source-level debugger, while
other bugs introduced by faulty transformations are harder to
analyze. To support both categories of users we propose
multilevel debugging that enables inspecting the program state and
controlling execution on different abstraction levels.</p>
      <p>To illustrate the usage of multi-level debuggers we consider
an example language extension for mbeddr: we introduce an
loop abstraction for C that allows users to specify iterations
with lower and upper boundaries. To test the generator of
this language abstraction, we start by writing a testcase
(an mbeddr extension). Listing 1 below shows the test code:
a main function invokes the testcase sumTesting, via a
test expression. This testcase uses the loop to add
up numbers from 0 to 10 in a variable sum. Finally, an
assert statement verifies that the value of sum equals 55.
If the assertion fails, the process returns a positive number,
representing the number of failed assertions.
1 testcase sumTesting {
2 int32 sum = 0;
3 loop [0 to 10] {
4 sum += it;
5 }
6 assert sum == 55;
7 }
8 int32 main() {
9 return test[sumTesting];
10 }</p>
      <p>Listing 1. Testing the loop generator</p>
      <p>Looking at the code shown in Listing 1 and considering the
semantics of our loop, the test should succeed. However, it
fails with a return code 1 indicating a failed assertion. Suppose
we have an interactive source-level debugger that allows us to
debug Listing 1. By using this debugger we can see that the
loop body is never reached. Instead, execution jumps directly
to the assert from the loop header. Since we cannot detect
the reason for this behavior on the source level, we are forced
to use the base language debugger with the generated code
shown in Listing 2. A starting point could be to locate the
source lines representing our loop. Since our generated code
only contains one while, this is trivial. In more complex
scenarios, this would require additional effort.
1 int __testcases2323() { 10 if(!(sum == 55)) {
2 int __failures = 0; 11 __failures++;
3 { int sum = 0; 12 }
4 { int __index = 10; 13 }
5 while(__index &lt;= 0) { 14 return __failures;
6 sum += __index; 15 }
7 __index++; 16 void main() {
8 } 17 return __testcases2323();
9 } 18 }</p>
      <p>Listing 2. Generated code for testing the loop statement
As we can see on line number 4 and 5 in Listing 2, the
initialization of the lower and upper bound was accidentally
swapped by the code generator. Therefore the while condition
never evaluates to true and the loop body is not entered. This
is exactly the behavior we experienced when debugging on the
source level. After identifying the bug, we can fix the problem
in the generator first. Afterwards, knowing which program
location caused the error, we can use a multi-level debugger to
debug on the intermediate level where the loop is reduced, but
all other abstractions are still present (see Listing 3). This way,
we can verify the bug fix we have made in the generator and
concentrate on the reduced loop while debugging, ignoring
irrelevant, generated details.
1 testcase sumTesting {
2 int sum = 0;
3 { int __index = 0;
4 while(__index &lt;= 10) {
5 sum += __index;
6 }
7 }
8 assert sum == 55;
9 }
10 int32 main() {
11 return test[sumTesting];
12 }</p>
      <p>
        Listing 3. Intermediate code used for debugging the loop
In the context of extensible languages we believe multi-level
debuggers support language engineers in the language
implementation and maintenance phase. Further, we believe
sourcelevel debuggers targeting users of these languages should be
built in a way to support multi-level debugging as well. That
is, program state and stepping behavior should be lifted
incrementally from base to source level, considering all program
modifications made by transformation rules in between. In
contrast, debuggers for extensible languages operating directly
between source and target level have limitations [
        <xref ref-type="bibr" rid="ref4">4</xref>
        ]. First, they
usually cannot support multiple generators per language and
multiple transformation rules per language construct. Second,
since they depend on the structure of the generated code,
modifying a code generator usually implies updating the
debugger implementation.
      </p>
    </sec>
    <sec id="sec-3">
      <title>III. THE MULDER FRAMEWORK</title>
      <p>The Multi-Level Debugger (MuLDer) framework presented
in this paper is based on an incremental approach lifting
program state from the target level to the currently investigated
abstraction level. To describe debugging semantics, debugger
developers associate language constructs with debug semantics
and specify rules in M2M and M2T transformations to lift the
program state from the generated level back to the original
level. These rules annotate the generated code, but do not
influence its semantics. They specify how the debugger should lift
the lower level program state and get processed incrementally
from target level to the currently investigated abstraction level.
Further, to imitate stepping behavior, the framework provides
two approaches: a control-flow based approach using
targetlevel breakpoints and a single-stepping based approach using
single-stepping functionality of an underlying GPL debugger.</p>
      <sec id="sec-3-1">
        <title>A. Architecture</title>
        <p>Fig. 2 illustrates with a Unified Modeling Language (UML)
component diagram the software components that make up
the framework architecture. Grey colored software
components and interfaces describe functionality required from the
language workbench (MPS in our reference implementation).
They comprise the following parts: ASG access via IASG,
possibility to retrieve control-flow information for a given
program that we require for imitating stepping behavior via
IControlFlowProvider, adding preference pages to configure
debugging via IPreferences and contribution of User Interface
(UI) components, e. g., a debugger view, via IUIContribution.</p>
        <p>White colored boxes represent abstractions, languages,
components and interfaces from MuLDer. Both Program</p>
      </sec>
      <sec id="sec-3-2">
        <title>State Abstractions &amp; Specification and Execution Control</title>
        <p>Abstractions &amp; Specification contain language abstractions and
specification languages used to describe program state lifting,
stepping behavior and translation of breakpoints. Following,
Program Annotator operates on these language abstractions
and accesses via IASG the ASG to automatically attach lifting
rules to ASG nodes (discussed later). Next, Debug Preferences
contributes via IPreferences and IUIContribution preference
pages to the language workbench. It also provides a UI to
manage these preferences, comprising options for defining the
currently selected stepping algorithm and configuring visible
debug information, e. g., lifting rules, in ASGs. Further,
Stepping Processor operates on the Execution Control Abstractions
and provides via IStepper an interface to imitate stepping
commands by using the currently selected stepping algorithm.
This component requires the interface ITLDebugger to invoke
target-level stepping commands, used by the single-stepping
algorithm, and to manage breakpoints, required by our
controlflow based stepping algorithms. Stepping Processor requires
for some of its algorithms the program state being accessed
from Call Stack Unwinder via IPState, access to the ASG via</p>
        <p>IASG and control-flow information being retrieved from
IControlFlowProvider. However, the latter is only required by the
control-flow related stepping algorithms. Next, the call stack is
lifted by Call Stack Unwinder, which operates on the Program
State Abstractions and requires ASG access via IASG, the
incrementally lifted base-level program state obtained via
IBL</p>
      </sec>
      <sec id="sec-3-3">
        <title>ProgramState, tracing information being accessed via ITraces</title>
        <p>and the lifted watch variables provided by Watch Variables</p>
      </sec>
      <sec id="sec-3-4">
        <title>Lifter via IWatchLifter. The previously described Multi-Level</title>
        <p>Tracer maintains tracing information describing how ASG
nodes are translated across all abstraction levels and provides
access to this information via ITraces. Finally, Watch Variables
Lifter lifts lower level watch variables and their associated
values based on the program state abstractions.</p>
      </sec>
      <sec id="sec-3-5">
        <title>B. Language Abstractions</title>
        <p>MuLDer provides a set of abstractions represented by
interfaces to specify debugging semantics of language constructs.
Debugger developers implement these interfaces by creating a
set of queries using a language extension for Base Language
(an extensible Java coming with MPS). We do not describe
these queries in detail, instead, we discuss those being required
by our case study in Section IV. The following list contains
all abstractions coming with MuLDer: Steppable lives inside
a Steppable Composite (e. g., statement list) and is a
language construct comparable to statement on which we
can invoke a stepping command. Next, Callable represents
a reusable code fragment similar to function and can be
invoked from other program locations via a Callable Call
(e. g., function call). Control Flow Provider is a Callable
and provides control-flow information for the contained code.
Watch Providers contribute watch variables to the debugger
view and are usually represented by variables. They are
contained in a nestable Scope Provider (e. g., statement
list) and resolve their value by a Value Provider, e. g., the
type of the variable.</p>
      </sec>
      <sec id="sec-3-6">
        <title>C. Value Contracts</title>
      </sec>
      <sec id="sec-3-7">
        <title>Value Contracts define default value lifting rules and the</title>
        <p>structure of watch variable values that Value Providers
contribute. While the structure is used for writing formal Value
Transformations (discussed later), we use the rule to lift
lowlevel watch variable values for which the generated (level
n) and origin Value Provider (level n-1) are the same. The
Program Annotator (see Section III-A) is responsible for
attaching these rules to intermediate ASGs after code generation.
Consider we use a base language type (a Value Provider) in
our program that is simply translated to text and not modified
by any of the code generators. This is one example where the</p>
      </sec>
      <sec id="sec-3-8">
        <title>Program Annotator would attach the default value lifting rule</title>
        <p>of this type to instances of each level, where the input node
of a type is the same type.</p>
        <p>We consider in Fig. 3 the Value Contract for the
mbeddr PointerType, consisting of a complex-value with
reference semantics that embodies one watch holding an
absent-value, the pointer target that is known at compile
time (e. g., an int type). The code snippet in the box below
shows the implementation of the default value lifting rule for
complex-value, other parts, e. g., for absent-value, are
not shown. In this rule, we use a Domain-Specific Language
(DSL) to return the textual presentation of the watch
variable value. However, as seen in the code completion menu,
we can also access other value properties, e. g., subvalue(s),
because we have defined the value to be a complex-value
and isNull as the value has reference semantics (*-&gt;).</p>
      </sec>
      <sec id="sec-3-9">
        <title>Value Transformations operate on Value Contracts and</title>
        <p>describe transformations of watch variable values being
associated with different Value Providers. To implement such
transformations, developers specify the structure of a source
and target watch and annotate the latter with lifting rules used
to construct the lifted value.</p>
        <p>To illustrate Value Transformations, we consider an example
from mbeddr, translating a value of pointer on char type
to a StringType value. For this purpose, we create the Value
Transformation partially shown in Fig. 4. First, we create value
structures for the source and target watch variable on top,
describing the former as PointerType with a child value that
is associated with a CharType. To specify source-watches,
we refer to information from Value Contracts of the
referenced Value Providers. After selecting a Value Provider,
the editor projects the value structure of the specified Value
Contract with the possibility to concretize absent-values.
In our example, PointerType embodies a watch of value
absent-value that we concretize with CharType, causing
the editor to project the primitive-value, coming from
the Value Contract for CharType. Next, we describe the
target-watch by referencing StringType, which projects the
primitive-value from the Value Contract. After describing
the structures, we continue with the default value lifting rule
for primitive-value that is shown in the figure below. For
this purpose, we use a regular expression that extracts a string
enclosed in two quotation marks. Similarly to Value Contracts,
the properties that we can access on watchable.value are
based on a value structure, source-watch in this context.</p>
      </sec>
      <sec id="sec-3-10">
        <title>E. Target to Base-Level Lifting</title>
        <p>Lifting the program state from target to base level is driven
by using identifiers whereas we perform lifting between other
levels by using references between ASG nodes. This section
describes the lifting rules and specification languages we
provide to lift program state from target to base level.</p>
        <p>Because program code is on base and target level similarly
structured, we usually have a one-to-one mapping between
base-level ASG nodes and target-level text lines. However,
we must track identifiers that we use to establish a mapping
between watch variables and stack frames from target and
base level. For this purpose we provide a set of Text 2 Model
(T2M) lifting rules in form of annotations attached to
baselevel ASG nodes and used to lift program state from target
level (text) to the base level (ASG). As we will later show in
Section III-F, the base level is also annotated with M2M lifting
rules, incrementally lifting program state from base level to
the level on which the user debugs his code. Hence, the base
level contains annotations to lift program state from target to
base level and from base level to the last intermediate level.</p>
        <p>The following list describes T2M annotations we
provide for lifting program state from target to base level.
T2MFrame2Frame annotates a base-level Callable and holds
its generated target-level identifier. We use this identifier
to associate the annotated Callable with target-level stack
frames. Next, T2MWatch2Watch annotates a Watch Provider
and holds its generated target-level identifier. Additionally,
this annotation refers to a Value Provider lifting the value.</p>
      </sec>
      <sec id="sec-3-11">
        <title>Following, T2MValueLifter annotates a Value Provider and</title>
        <p>refers either to a Value Transformation or to a different Value
Provider delegating the program state lifting to it. Finally,
T2MConstant tracks generated identifiers, e. g., enum literals.</p>
        <p>MPS’ M2T transformation language is extensible and
translates to Base Language. We have exploited this fact by
developing a declarative language extension to describe T2M
annotations for a given M2T transformation. For this language
extension we generate code that attaches the respective
annotation to the transformed base-level ASG node at generation
time. Fig. 5 below shows parts of the annotated M2T
transformation for Argument, a Watch Provider from mbeddr. We have
annotated this transformation with an M2TWatchProvider
annotation (@WatchProvider on top), attached a M2TIdentifier
to node.name (@IdentifierProvider) and a M2TValue
to node.type (@ValueProvider). During transformation
execution an T2MWatch2Watch annotation gets attached to
the transformed base-level Argument comprising information
about the generated identifier and the Value Provider.</p>
      </sec>
      <sec id="sec-3-12">
        <title>F. Incremental Lifting</title>
        <p>In contrast to the language extension for MPS’ M2T
transformation language, we did not extend MPS’ generator
language. Instead, we provide a set of rules to be used in
transformations for annotating the generated code.</p>
        <p>To unwind the call stack we provide three different
annotations: M2MInlineFrame annotates a Callable for which
we inline its associated stack frames on the higher level,
M2MFrame2Frame annotates a Callable as well, but lifts
its stack frames to a Callable from the next higher level.
Finally, M2MOutlineFrame annotates a generated ASG node
originating from a Callable for which we outline a stack frame.</p>
        <p>To lift watch variables, we provide two annotations, both
annotate a Watch Provider: M2MWatch2Watch and
M2MChildWatches2Watches. The former lifts watch variables contributed
by the annotated Watch Provider to another Watch Provider
from the next higher level. In contrast, the latter lifts child
values (also contributed by Watch Providers) as top-level
watch variables to the next higher level.</p>
        <p>To lift watch variable values originating from Value
Providers, we provide three different annotations: first,</p>
      </sec>
      <sec id="sec-3-13">
        <title>M2MLiftValue refers to a default value lifting rule and is</title>
        <p>automatically attached by the Program Annotator, second,</p>
      </sec>
      <sec id="sec-3-14">
        <title>M2MGeneratedDelegateToValueProvider is created by the de</title>
        <p>bugger developer and refers to another Value Provider
delegating value lifting to it. Finally, M2MGeneratedValueLifter
is also manually created and refers to a Value Transformation
being used to lift the value representation of a generated Value
Provider. We have demonstrated in Section III-D a Value
Transformation for unveiling a string literal from a pointer
on char. Fig. 6 below shows the transformation rule for</p>
      </sec>
      <sec id="sec-3-15">
        <title>StringType with a M2MGeneratedValueLifter being attached</title>
        <p>to the generated type and referring to our previously created
Value Transformation (liftCharPointer2StringType).
mbeddr comes with an extension to declare and
instantiate components and mock components, both illustrated by
the example in Listing 4. We declare in this listing two
interfaces, ILogger representing a logging service and
IAdder for adding up two numbers. Further, we implement a mock
Logger that provides the interface ILogger and contains a
sequence modeling with sequence steps the order in which
operation calls are expected. Next, we declare a component
Adder that requires ILogger to log added values and
provides an implementation of IAdder to add up two
numbers. This component also contains runnables, which have
arguments, a return type, a body (statement list)
containing the implementation, and a trigger. While setup initializes
the logger and acts as a constructor (OnInit), the other
runnable is bound to the provided port and contains the
C implementation to add up both arguments. To instantiate
both components, we create an instance configuration
that connects both instances based on their provided and
required ports. Finally, we create a main function that
invokes a testcase testing the Adder component. In this test,
we initialize both components (initInstances) and invoke
the add operation on the adder instance. Afterwards, we
validate the result using assertEquals and verify the call
sequence on Logger using assertMock.</p>
        <p>With MuLDer, debugging support is always built per
language construct. Hence, a debugger built with this
framework consists of debugging implementations for different
language constructs. In this case study we build debugging
support for the mock component language. Because this
language extends the mbeddr base language (C) and gets
reduced to mbeddr’s components language, we expect to
have functioning debugging support for language constructs
from these languages. Debugging support for all languages
that are used on the source level and intermediate levels is
a prerequisite of our approach. First, we define debugging
semantics for mock component and sequence step. For
other language constructs from the components language
debugging semantics are already defined. mock component
extends component, which already implements Value Provider
and Scope Provider, hence, we do not require any additional
interface implementations. Because sequence steps can be
invoked, we implement Callable in the language construct
returning the step index as name for contributed stack frames.
Further, because sequence steps can contain an optional
body in which stepping functionality can be used, we
implement Steppable Composite and return the contained body in
the required query. statement list comes from mbeddr C
which already specifies the required interfaces.</p>
        <p>Next, in Fig. 7 below we annotate the transformation rule
for mocks, describing the program state lifting. First, we
create fields (used for storing state) that track the number
of failed expectations and overall call counts. Second, we
1 mock Logger {
2 provides ILogger logger
3 sequence {
4 0:logger.init
5 1:logger.log
6 }
7 }
8 instance configuration cfg {
9 Adder adder
10 LoggerMock logger
11 connect logger to adder
12 }
copy content (COPY_SRCL) from our mock to the component.
Third, we generate a runnable being used by the generator
for assertMock to request the number of failed expectations.
Fourth, we generate at the bottom of the component for
each operation of our provided ports a runnable that
inherits the signature and has a trigger being bound to the
operation and the associated provided port. Stack frames
for these generated runnables are not lifted, instead, we
generate for each sequence step a statement list being
annotated with an M2MOutlineFrame annotation, outlining a
stack frame for the sequence step. The specification shown
at the bottom of Fig. 7 configures these stack frames: program
counters for outer stack frames are redefined with the current
node and we associate unwound stack frames with the higher
level sequence step. The statement list we generate
from sequence steps increments the call count and verifies
that current and expected call count are equal, if not, we
increment failed expectations. Further, we annotate the generated
component with an M2MGeneratedDelegateToValueProvider
annotation, referring to the Value Transformation shown in
the middle. This transformation constructs a complex-value
with the mock name as top-level value, whereas child values,
content of kind Watch Provider, are lifted from subvalues of
the current watch variable value.</p>
        <p>MuLDer is a language-oriented and incremental framework
enabling multi-level debugging for extensible languages. By
using the underlying approach, debugger developers specify
debugging behavior in two steps. First, they describe
debugging semantics of language constructs, e. g., for callables or
variables, second, they annotate transformations with rules
that appear hereby on intermediate level ASGs. Debuggers
built with our approach use these rules at debug time to
lift program state from a lower level back to the origin
level, incrementally across intermediate levels. Because we
describe these lifting rules inside transformations, we
support multiple generators per language and multiple
transformation rules per language construct. Consider the language
extensions for mock components from Section IV, where
we have described program state lifting from components
back to mock components, ignoring how components are
further translated towards the target level. Because we specify
debugging behavior between generated and origin level,
debuggers built with our approach are not affected by changes
in lower level generators, an important requirement in the
extensible language context. While MuLDer can be used to
build debugging support for many extensible languages, the
underlying approach has some limitations that we discuss next.</p>
      </sec>
      <sec id="sec-3-16">
        <title>A. Statically Typed Languages</title>
        <p>To describe the lifting of watch variable values, our
approach requires variables to be associated with a type
(Value Provider). This is no limitation for mbeddr, because the
language is statically typed. However, due to this limitation we
cannot support dynamically typed languages, e. g., JavaScript.</p>
      </sec>
      <sec id="sec-3-17">
        <title>B. Stage-wise ASG Node Modifications</title>
        <p>Output nodes of one transformation cannot be transformed
by the same generator again, because we would lose
information about code modification this way. Instead, when using our
approach, these output nodes can only be modified by another
code generator being executed afterwards.</p>
      </sec>
      <sec id="sec-3-18">
        <title>C. Performance Overhead</title>
        <p>The runtime behavior of lifting program state is driven by
the number of instantiated abstractions (a), e. g., Callables,
and the complexity of their associated specifications (s).
Further, because we lift program state incrementally, the number
of intermediate levels (l) is another factor that influences
runtime behavior. Because we build interactive debuggers,
runtime performance is a critical aspect, as users expect fast
feedback from the tool. However, the debugging performance
can dramatically decrease by increasing the program size over
time and using more high-level languages.</p>
        <p>
          Renggli et al. [
          <xref ref-type="bibr" rid="ref5">5</xref>
          ] describe a source-level debugger for
Helvetia, a tool that allows users to embed DSLs into Smalltalk
host programs, extending Smalltalk with new syntax and
semantics. While Helvetia translates DSL code directly to
Smalltalk, our approach targets multi-stage transformations,
reducing code incrementally to a base language. Further,
we show program state based on the currently investigated
abstraction level, while the Helvetia debugger shows this
information in terms of the generated Smalltalk code.
        </p>
        <p>MPS comes with an extensible Java and a debugger for this
language. While debuggers built with our approach show the
program state and imitate stepping commands based on the
currently investigated abstraction level, MPS’ Java debugger
shows the target-level call stack and directs each stepping
command to the target level, not imitating the expected behavior.</p>
        <p>
          We have built a source-level debugger framework for
mbeddr that maps debug information directly between target
and source level [
          <xref ref-type="bibr" rid="ref6">6</xref>
          ]. While MuLDer enables encapsulated
debugger modules that are not affected by changes in lower
level generators, debuggers built with the mbeddr framework
depend on the structure of the generated target-level code.
Hence, modifying lower level generators usually implies
updating debuggers that have been built with this framework.
        </p>
        <p>
          Mierlo [
          <xref ref-type="bibr" rid="ref7">7</xref>
          ] describes a debugging approach for modeling
languages. With this approach, the modal behavior of a
simulator for the language is described as a state chart, which
is extended with debugging information. While this approach
targets modeling languages with a fixed set of language
constructs, our approach targets extensible languages. Further,
the approach presented by Mierlo is used for debugging
models on the source-level, while our approach allows
multilevel debugging. Finally, Mierlo requires language engineers to
describe the executable semantics by using state charts, while
with our approach debugging semantics of language constructs
are described and transformation rules are annotated.
        </p>
      </sec>
      <sec id="sec-3-19">
        <title>B. Multi-Level Debuggers</title>
        <p>
          Florisson [
          <xref ref-type="bibr" rid="ref8">8</xref>
          ] describes a multi-level debugger for Cython, a
language allowing users to mix C and Python code in the same
program. Such programs are compiled to C and integrated
with a Python Application Programming Interface (API) for
C, thus being accessible from Python. The resulting C code is
further translated to a CPython extension module, which can
afterwards be called from regular Python code. While code
written with Cython interacts with Python code, users cannot
debug it with a Python debugger. For this purpose, Florisson
proposes a multi-level debugger that allows users to debug
Python, Cython and C code simultaneously. While debugging
calls from Python to Cython, the debugger follows control
flow skipping the C abstraction level, such as calls to the
Python interpreter (Python API). Thus, during stepping, the
user will see Python code calling Cython code calling C code
and can investigate the program state in terms of the respective
language. Our approach also covers debugging mixed language
programs, however, we support multiple abstraction levels and
do not switch between them while performing a stepping
command. Instead, our users switch manually between the
various abstraction levels. Additionally, we target extensible
languages, while Cython, Python and C have a fixed syntax.
        </p>
        <p>
          Xia et al. [
          <xref ref-type="bibr" rid="ref9">9</xref>
          ] present in their work multi-level debugging, an
approach for automatically detecting bugs in transformation
rules. This approach is based on a set of error checking
algorithms that analyze sequential and parallel aspects of
programs created during transformation. While their approach
automatically detects bugs, we allow users to debug their
program execution on the source level and intermediate levels
created by transformation rules.
        </p>
        <p>
          Mannadiar and Vangheluwe [
          <xref ref-type="bibr" rid="ref10">10</xref>
          ] describe a debugger for a
language that is used to model mobile applications. Because
their modeling tool enables tracing across intermediate levels
created during code generation, they can debug their model by
inspecting on each intermediate level the currently involved
model elements. While they provide tracing for intermediate
representations, we allow interactive debugging on each level.
        </p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>VII. SUMMARY AND FUTURE WORK</title>
      <p>In this paper we have presented an incremental approach
to build debuggers for extensible languages. While this
approach enables source-level debugging for language users,
it additionally allows language engineers to debug programs
on intermediate levels to analyze bugs introduced by faulty
transformation rules. Further, we have illustrated the MuLDer
framework, an MPS-based implementation of this approach.
We have used this framework to build a multi-level debugger
for mbeddr and demonstrated in this paper how debugging
behavior for mock components is implemented.</p>
      <p>In the future, we plan to use MuLDer to build debuggers
for other extensible languages and in other workbenches to
evaluate its genericity. Additionally, we plan to extend the
specification languages coming with MuLDer for specifying
not only debuggers, but also interpreters to allow multi-level
interpretation of extensible language programs.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1] JetBrains, “
          <article-title>Meta Programming System</article-title>
          ,” http://jetbrains.com/mps,
          <year>2015</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>M.</given-names>
            <surname>Voelter</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D.</given-names>
            <surname>Ratiu</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B.</given-names>
            <surname>Schaetz</surname>
          </string-name>
          , and
          <string-name>
            <given-names>B.</given-names>
            <surname>Kolb</surname>
          </string-name>
          , “
          <article-title>Mbeddr: An Extensible C-based Programming Language and IDE for Embedded Systems</article-title>
          ,” in
          <source>Proceedings of the 3rd Annual Conference on Systems, Programming</source>
          , and
          <article-title>Applications: Software for Humanity, ser</article-title>
          .
          <source>SPLASH '12</source>
          . New York, NY, USA: ACM,
          <year>2012</year>
          , pp.
          <fpage>121</fpage>
          -
          <lpage>140</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>E.</given-names>
            <surname>Bousse</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Corley</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B.</given-names>
            <surname>Combemale</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J. G.</given-names>
            <surname>Gray</surname>
          </string-name>
          , and
          <string-name>
            <given-names>B.</given-names>
            <surname>Baudry</surname>
          </string-name>
          , “
          <article-title>Supporting efficient and advanced omniscient debugging for xdsmls,”</article-title>
          <source>in Proceedings of the 2015 ACM SIGPLAN International Conference on Software Language Engineering</source>
          , SLE 2015, Pittsburgh, PA, USA, October
          <volume>25</volume>
          -
          <issue>27</issue>
          ,
          <year>2015</year>
          ,
          <string-name>
            <given-names>R. F.</given-names>
            <surname>Paige</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D. D.</given-names>
            <surname>Ruscio</surname>
          </string-name>
          , and M. Völter, Eds. ACM,
          <year>2015</year>
          , pp.
          <fpage>137</fpage>
          -
          <lpage>148</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>D.</given-names>
            <surname>Pavletic</surname>
          </string-name>
          and
          <string-name>
            <given-names>S. A.</given-names>
            <surname>Raza</surname>
          </string-name>
          ,
          <article-title>“Multi-Level Debugging for Extensible Languages,” Softwaretechnik-Trends</article-title>
          , vol.
          <volume>35</volume>
          , no.
          <issue>1</issue>
          ,
          <year>2015</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>L.</given-names>
            <surname>Renggli</surname>
          </string-name>
          ,
          <string-name>
            <given-names>T.</given-names>
            <surname>Gîrba</surname>
          </string-name>
          , and
          <string-name>
            <given-names>O.</given-names>
            <surname>Nierstrasz</surname>
          </string-name>
          , “
          <article-title>Embedding Languages without Breaking Tools,” in ECOOP 2010 -</article-title>
          <string-name>
            <surname>Object-Oriented</surname>
            <given-names>Programming</given-names>
          </string-name>
          ,
          <source>24th European Conference</source>
          , Maribor, Slovenia,
          <source>ser. Lecture Notes in Computer Science</source>
          , vol.
          <volume>6183</volume>
          . Springer,
          <year>June 2010</year>
          , pp.
          <fpage>380</fpage>
          -
          <lpage>404</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>D.</given-names>
            <surname>Pavletic</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Voelter</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S. A.</given-names>
            <surname>Raza</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B.</given-names>
            <surname>Kolb</surname>
          </string-name>
          , and T. Kehrer, “
          <source>Extensible Debugger Framework for Extensible Languages,” in 20th Ada-Europe International Conference on Reliable Software Technologies</source>
          , Madrid Spain, June 22-26,
          <year>2015</year>
          , Proceedings,
          <source>ser. Lecture Notes in Computer Science</source>
          , vol.
          <volume>9111</volume>
          . Springer,
          <year>2015</year>
          , pp.
          <fpage>33</fpage>
          -
          <lpage>49</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>S. V.</given-names>
            <surname>Mierlo</surname>
          </string-name>
          , “
          <article-title>Explicit modelling of model debugging and experimentation</article-title>
          ,”
          <source>in Proceedings of Doctoral Symposium co-located with 17th International Conference on Model Driven Engineering Languages and Systems</source>
          (
          <year>2014</year>
          ), Valencia, Spain,
          <year>September 30</year>
          ,
          <year>2014</year>
          ., ser. CEUR Workshop Proceedings, B. Baudry, Ed., vol.
          <volume>1321</volume>
          ,
          <year>2014</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>M.</given-names>
            <surname>Florisson</surname>
          </string-name>
          ,
          <article-title>“Multi-Level Debugging for Cython,” 14th Twente Student Conference on IT</article-title>
          , vol.
          <volume>14</volume>
          , no.
          <issue>1</issue>
          ,
          <string-name>
            <surname>Jan</surname>
          </string-name>
          .
          <year>2011</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <given-names>R.</given-names>
            <surname>Xia</surname>
          </string-name>
          ,
          <string-name>
            <given-names>T.</given-names>
            <surname>Elmas</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S. A.</given-names>
            <surname>Kamil</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Fox</surname>
          </string-name>
          , and
          <string-name>
            <given-names>K.</given-names>
            <surname>Sen</surname>
          </string-name>
          ,
          <article-title>“Multi-level Debugging for Multi-stage</article-title>
          , Parallelizing Compilers,” EECS Department, University of California, Berkeley, Tech. Rep.,
          <year>Dec 2012</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <given-names>R.</given-names>
            <surname>Mannadiar</surname>
          </string-name>
          and
          <string-name>
            <given-names>H.</given-names>
            <surname>Vangheluwe</surname>
          </string-name>
          , “Debugging in Domain-Specific Modelling,” in Software Language Engineering - Third International Conference, SLE 2010, Eindhoven,
          <source>The Netherlands, October 12-13</source>
          ,
          <year>2010</year>
          , Revised Selected Papers,
          <source>ser. Lecture Notes in Computer Science</source>
          , vol.
          <volume>6563</volume>
          . Berlin, Heidelberg: Springer,
          <year>2010</year>
          , pp.
          <fpage>276</fpage>
          -
          <lpage>285</lpage>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>