=Paper= {{Paper |id=Vol-1760/paper3 |storemode=property |title=Interactive Debugging for Extensible Languages in Multi-Stage Transformation Environments |pdfUrl=https://ceur-ws.org/Vol-1760/paper3.pdf |volume=Vol-1760 |authors=Domenik Pavletic,Kim Haßlbauer |dblpUrl=https://dblp.org/rec/conf/models/PavleticH16 }} ==Interactive Debugging for Extensible Languages in Multi-Stage Transformation Environments== https://ceur-ws.org/Vol-1760/paper3.pdf
 Interactive Debugging for Extensible Languages in
      Multi-Stage Transformation Environments
                           Domenik Pavletic                                             Kim Haßlbauer
                                itemis AG                                             Stuttgart, Germany
                           Stuttgart, Germany                                    kim.hasslbauer@gmail.com
                          pavletic@itemis.com



   Abstract—Extensible languages have a base language that              and usually come with generator frameworks to build multi-
can be extended incrementally with new language extensions,             stage transformations. JetBrains Meta Programming System
forming a stack with high-level languages on top and lower level        (MPS) [1] is a workbench that supports the development of
languages at the bottom. Programs written with these languages
are usually a mixture of code using base language and several           extensible languages, mbeddr [2] is one of these languages
language extensions. These extensions come with generators that         built with MPS. This language is based on C and comes with a
translate higher level language constructs to lower levels and          set of language extensions for embedded software engineering.
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.         Fig. 1. The colors of ASG nodes indicate how M2M and M2T code generators
   In this paper we present an approach to build multi-level            transform extensible language programs incrementally across different stages
                                                                        from source to base level and ultimately to text (target level)
debuggers for extensible languages that allow language users to
debug their code on the source level and language engineers to             Runtime bugs that appear in extensible language programs
debug on intermediate levels created during code generation. We         during execution can either be introduced on the source-
illustrate this approach with an implementation for the MPS
language workbench and mbeddr C, an extensible C language.              level by language users or on intermediate levels (e. g.,
   Index Terms—Formal languages, Software debugging.                    level 1.0 in the figure above) by language engineers through
                                                                        transformation rules. While language users require a source-
                      I. I NTRODUCTION                                  level debugger to analyze bugs introduced by themselves,
   Extensible languages are used to develop software systems            language engineers often investigate bugs introduced through
on a higher level of abstraction and consist of a base language,        faulty transformation rules by debugging the generated code
usually a General Purpose Language (GPL), which can be                  or the transformation process. Analyzing the transformation
extended with new, usually domain-specific, language exten-             process, e. g., by using an omniscient debugger [3], allows
sions on the syntactical and semantic level. Hence, extensible          language engineers to identify the rule that introduced a faulty
language programs comprise different languages, residing on             code segment and why this rule was executed, however, this
different abstraction levels. Each of these extensions comes            approach does not support analyzing the runtime behavior of
with a generator that translates code to a more concrete                this generated code segment. Further, generators may have
language. This translation happens incrementally from higher            modified the program structure, e. g., by changing identifiers
levels to lower ones, until we get a pure base language                 or the structure of statements. This abstraction mismatch
program. Fig. 1 shows the process from transforming the                 makes it hard to debug the execution of the generated code.
source-level Abstract Syntax Graph (ASG) of an extensible                  We present in this paper an approach to build multi-
language program to the base level via Model 2 Model                    level debuggers for extensible languages. These debuggers
(M2M) transformations and ultimately to text (target level) via         enable language engineers to debug programs on different
Model 2 Text (M2T) transformations. Graphs inside the boxes             levels, thus allowing them to analyze the runtime behavior of
represent ASGs of the respective abstraction level, colors              code generated from faulty transformation rules. Further, and
indicate structural modifications.                                      essential, these debuggers imitate stepping behavior and show
   The effort for building such extensible languages can be             program state based on the languages used on the investigated
reduced by using a language workbench, an Integrated Devel-             abstraction level, which can be an intermediate level that has
opment Environment (IDE) for language engineering. These                been generated by a faulty transformation. This approach also
tools provide facilities to implement language definitions              enables source-level debugging targeting language users.



                                                                   19
           II. D EBUGGING E XTENSIBLE L ANGUAGES                            the reason for this behavior on the source level, we are forced
                                                                            to use the base language debugger with the generated code
   Interactive debuggers allow users to inspect and animate the
                                                                            shown in Listing 2. A starting point could be to locate the
execution of a program. These debuggers usually operate on
                                                                            source lines representing our loop. Since our generated code
the source level and provide, depending on the actual tool,
                                                                            only contains one while, this is trivial. In more complex
different functionalities: users can put breakpoints on source
                                                                            scenarios, this would require additional effort.
lines or memory addresses. Further, they can use stepping
                                                                            1   int __testcases2323() {               10         if(!(sum == 55)) {
commands to animate the execution and inspect the program                   2      int __failures = 0;                11            __failures++;
state. Some debuggers even allow users to manipulate values                 3      {    int sum = 0;                  12         }
                                                                            4         { int __index = 10;             13      }
of watch variables or interpret arbitrary expressions.                      5            while(__index <= 0) {        14      return __failures;
   Programming languages usually support source-level de-                   6               sum += __index;           15   }
                                                                            7               __index++;                16   void main() {
bugging, as multi-level debugging makes little sense in this                8            }                            17     return __testcases2323();
context: first, these languages are usually not extensible and              9         }                               18   }

get transformed to the target language, via a single interme-                       Listing 2. Generated code for testing the loop statement
diate language. Debugging intermediate levels is not useful to
                                                                            As we can see on line number 4 and 5 in Listing 2, the
language users or compiler engineers, as this code is usually
                                                                            initialization of the lower and upper bound was accidentally
similar to the target level representation. Second, language
                                                                            swapped by the code generator. Therefore the while condition
users are often not familiar with the underlying target language
                                                                            never evaluates to true and the loop body is not entered. This
(e. g., assembler). They rather consider the compiler as a
                                                                            is exactly the behavior we experienced when debugging on the
black box that is configurable via parameters. With extensible
                                                                            source level. After identifying the bug, we can fix the problem
languages, the situation is different: while extensions have a
                                                                            in the generator first. Afterwards, knowing which program
common base language, they can be stacked in an arbitrary
                                                                            location caused the error, we can use a multi-level debugger to
hierarchical way. That is, new language extensions can be
                                                                            debug on the intermediate level where the loop is reduced, but
hooked into the generation process. This flexibility increases
                                                                            all other abstractions are still present (see Listing 3). This way,
the complexity and the possibility for introducing bugs into
                                                                            we can verify the bug fix we have made in the generator and
the program through faulty transformations. As described at
                                                                            concentrate on the reduced loop while debugging, ignoring
the end of the section above, bugs introduced by language
                                                                            irrelevant, generated details.
users can be analyzed with a source-level debugger, while
                                                                            1   testcase sumTesting {             7         }
other bugs introduced by faulty transformations are harder to               2      int sum = 0;                   8         assert sum == 55;
analyze. To support both categories of users we propose multi-              3      { int __index = 0;             9    }
                                                                            4         while(__index <= 10) {     10    int32 main() {
level debugging that enables inspecting the program state and               5            sum += __index;         11      return test[sumTesting];
controlling execution on different abstraction levels.                      6         }                          12    }

   To illustrate the usage of multi-level debuggers we consider                      Listing 3. Intermediate code used for debugging the loop
an example language extension for mbeddr: we introduce an
                                                                            In the context of extensible languages we believe multi-level
loop abstraction for C that allows users to specify iterations
                                                                            debuggers support language engineers in the language imple-
with lower and upper boundaries. To test the generator of
                                                                            mentation and maintenance phase. Further, we believe source-
this language abstraction, we start by writing a testcase
                                                                            level debuggers targeting users of these languages should be
(an mbeddr extension). Listing 1 below shows the test code:
                                                                            built in a way to support multi-level debugging as well. That
a main function invokes the testcase sumTesting, via a
                                                                            is, program state and stepping behavior should be lifted incre-
test expression. This testcase uses the loop to add
                                                                            mentally from base to source level, considering all program
up numbers from 0 to 10 in a variable sum. Finally, an
                                                                            modifications made by transformation rules in between. In
assert statement verifies that the value of sum equals 55.
                                                                            contrast, debuggers for extensible languages operating directly
If the assertion fails, the process returns a positive number,
                                                                            between source and target level have limitations [4]. First, they
representing the number of failed assertions.
                                                                            usually cannot support multiple generators per language and
1   testcase sumTesting {            6      assert sum == 55;
2      int32 sum = 0;                7   }
                                                                            multiple transformation rules per language construct. Second,
3      loop [0 to 10] {              8   int32 main() {                     since they depend on the structure of the generated code,
4          sum += it;                9      return test[sumTesting];
5      }                            10   }
                                                                            modifying a code generator usually implies updating the
                                                                            debugger implementation.
                  Listing 1. Testing the loop generator

   Looking at the code shown in Listing 1 and considering the                               III. T HE M U LD ER F RAMEWORK
semantics of our loop, the test should succeed. However, it                   The Multi-Level Debugger (MuLDer) framework presented
fails with a return code 1 indicating a failed assertion. Suppose           in this paper is based on an incremental approach lifting
we have an interactive source-level debugger that allows us to              program state from the target level to the currently investigated
debug Listing 1. By using this debugger we can see that the                 abstraction level. To describe debugging semantics, debugger
loop body is never reached. Instead, execution jumps directly               developers associate language constructs with debug semantics
to the assert from the loop header. Since we cannot detect                  and specify rules in M2M and M2T transformations to lift the



                                                                       20
program state from the generated level back to the original                   IASG and control-flow information being retrieved from ICon-
level. These rules annotate the generated code, but do not influ-             trolFlowProvider. However, the latter is only required by the
ence its semantics. They specify how the debugger should lift                 control-flow related stepping algorithms. Next, the call stack is
the lower level program state and get processed incrementally                 lifted by Call Stack Unwinder, which operates on the Program
from target level to the currently investigated abstraction level.            State Abstractions and requires ASG access via IASG, the
Further, to imitate stepping behavior, the framework provides                 incrementally lifted base-level program state obtained via IBL-
two approaches: a control-flow based approach using target-                   ProgramState, tracing information being accessed via ITraces
level breakpoints and a single-stepping based approach using                  and the lifted watch variables provided by Watch Variables
single-stepping functionality of an underlying GPL debugger.                  Lifter via IWatchLifter. The previously described Multi-Level
                                                                              Tracer maintains tracing information describing how ASG
A. Architecture                                                               nodes are translated across all abstraction levels and provides
   Fig. 2 illustrates with a Unified Modeling Language (UML)                  access to this information via ITraces. Finally, Watch Variables
component diagram the software components that make up                        Lifter lifts lower level watch variables and their associated
the framework architecture. Grey colored software compo-                      values based on the program state abstractions.
nents and interfaces describe functionality required from the
                                                                              B. Language Abstractions
language workbench (MPS in our reference implementation).
They comprise the following parts: ASG access via IASG,                          MuLDer provides a set of abstractions represented by inter-
possibility to retrieve control-flow information for a given                  faces to specify debugging semantics of language constructs.
program that we require for imitating stepping behavior via                   Debugger developers implement these interfaces by creating a
IControlFlowProvider, adding preference pages to configure                    set of queries using a language extension for Base Language
debugging via IPreferences and contribution of User Interface                 (an extensible Java coming with MPS). We do not describe
(UI) components, e. g., a debugger view, via IUIContribution.                 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
Fig. 2. UML component diagram showing the MuLDer software architecture
                                                                              view and are usually represented by variables. They are
   White colored boxes represent abstractions, languages,                     contained in a nestable Scope Provider (e. g., statement
components and interfaces from MuLDer. Both Program                           list) and resolve their value by a Value Provider, e. g., the
State Abstractions & Specification and Execution Control                      type of the variable.
Abstractions & Specification contain language abstractions and
specification languages used to describe program state lifting,               C. Value Contracts
stepping behavior and translation of breakpoints. Following,                     Value Contracts define default value lifting rules and the
Program Annotator operates on these language abstractions                     structure of watch variable values that Value Providers con-
and accesses via IASG the ASG to automatically attach lifting                 tribute. While the structure is used for writing formal Value
rules to ASG nodes (discussed later). Next, Debug Preferences                 Transformations (discussed later), we use the rule to lift low-
contributes via IPreferences and IUIContribution preference                   level watch variable values for which the generated (level
pages to the language workbench. It also provides a UI to                     n) and origin Value Provider (level n-1) are the same. The
manage these preferences, comprising options for defining the                 Program Annotator (see Section III-A) is responsible for at-
currently selected stepping algorithm and configuring visible                 taching these rules to intermediate ASGs after code generation.
debug information, e. g., lifting rules, in ASGs. Further, Step-              Consider we use a base language type (a Value Provider) in
ping Processor operates on the Execution Control Abstractions                 our program that is simply translated to text and not modified
and provides via IStepper an interface to imitate stepping                    by any of the code generators. This is one example where the
commands by using the currently selected stepping algorithm.                  Program Annotator would attach the default value lifting rule
This component requires the interface ITLDebugger to invoke                   of this type to instances of each level, where the input node
target-level stepping commands, used by the single-stepping                   of a type is the same type.
algorithm, and to manage breakpoints, required by our control-                   We consider in Fig. 3 the Value Contract for the
flow based stepping algorithms. Stepping Processor requires                   mbeddr PointerType, consisting of a complex-value with
for some of its algorithms the program state being accessed                   reference semantics that embodies one watch holding an
from Call Stack Unwinder via IPState, access to the ASG via                   absent-value, the pointer target that is known at compile



                                                                         21
time (e. g., an int type). The code snippet in the box below              E. Target to Base-Level Lifting
shows the implementation of the default value lifting rule for               Lifting the program state from target to base level is driven
complex-value, other parts, e. g., for absent-value, are                  by using identifiers whereas we perform lifting between other
not shown. In this rule, we use a Domain-Specific Language                levels by using references between ASG nodes. This section
(DSL) to return the textual presentation of the watch                     describes the lifting rules and specification languages we
variable value. However, as seen in the code completion menu,             provide to lift program state from target to base level.
we can also access other value properties, e. g., subvalue(s),               Because program code is on base and target level similarly
because we have defined the value to be a complex-value                   structured, we usually have a one-to-one mapping between
and isNull as the value has reference semantics (*->).                    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 base-
                                                                          level ASG nodes and used to lift program state from target
                                                                          level (text) to the base level (ASG). As we will later show in
          Fig. 3. Value Contract for the mbeddr PointerType
                                                                          Section III-F, the base level is also annotated with M2M lifting
D. Value Transformations                                                  rules, incrementally lifting program state from base level to
                                                                          the level on which the user debugs his code. Hence, the base
   Value Transformations operate on Value Contracts and                   level contains annotations to lift program state from target to
describe transformations of watch variable values being as-               base level and from base level to the last intermediate level.
sociated with different Value Providers. To implement such                   The following list describes T2M annotations we pro-
transformations, developers specify the structure of a source             vide for lifting program state from target to base level.
and target watch and annotate the latter with lifting rules used          T2MFrame2Frame annotates a base-level Callable and holds
to construct the lifted value.                                            its generated target-level identifier. We use this identifier
   To illustrate Value Transformations, we consider an example            to associate the annotated Callable with target-level stack
from mbeddr, translating a value of pointer on char type                  frames. Next, T2MWatch2Watch annotates a Watch Provider
to a StringType value. For this purpose, we create the Value              and holds its generated target-level identifier. Additionally,
Transformation partially shown in Fig. 4. First, we create value          this annotation refers to a Value Provider lifting the value.
structures for the source and target watch variable on top,               Following, T2MValueLifter annotates a Value Provider and
describing the former as PointerType with a child value that              refers either to a Value Transformation or to a different Value
is associated with a CharType. To specify source-watches,                 Provider delegating the program state lifting to it. Finally,
we refer to information from Value Contracts of the ref-                  T2MConstant tracks generated identifiers, e. g., enum literals.
erenced Value Providers. After selecting a Value Provider,                   MPS’ M2T transformation language is extensible and trans-
the editor projects the value structure of the specified Value            lates to Base Language. We have exploited this fact by
Contract with the possibility to concretize absent-values.                developing a declarative language extension to describe T2M
In our example, PointerType embodies a watch of value                     annotations for a given M2T transformation. For this language
absent-value that we concretize with CharType, causing                    extension we generate code that attaches the respective anno-
the editor to project the primitive-value, coming from                    tation to the transformed base-level ASG node at generation
the Value Contract for CharType. Next, we describe the                    time. Fig. 5 below shows parts of the annotated M2T transfor-
target-watch by referencing StringType, which projects the                mation for Argument, a Watch Provider from mbeddr. We have
primitive-value from the Value Contract. After describing                 annotated this transformation with an M2TWatchProvider an-
the structures, we continue with the default value lifting rule           notation (@WatchProvider on top), attached a M2TIdentifier
for primitive-value that is shown in the figure below. For                to node.name (@IdentifierProvider) and a M2TValue
this purpose, we use a regular expression that extracts a string          to node.type (@ValueProvider). During transformation
enclosed in two quotation marks. Similarly to Value Contracts,            execution an T2MWatch2Watch annotation gets attached to
the properties that we can access on watchable.value are                  the transformed base-level Argument comprising information
based on a value structure, source-watch in this context.                 about the generated identifier and the Value Provider.
                                                                          F. Incremental Lifting
                                                                             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.
                                                                             To unwind the call stack we provide three different an-
                                                                          notations: M2MInlineFrame annotates a Callable for which
      Fig. 4. Value Transformation for constructing a string value        we inline its associated stack frames on the higher level,



                                                                     22
                                                                                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 num-
                                                                                bers. This component also contains runnables, which have
                                                                                arguments, a return type, a body (statement list) contain-
                                                                                ing the implementation, and a trigger. While setup initializes
                                                                                the logger and acts as a constructor (OnInit), the other
      Fig. 5. Lifting target-level watch variables for mbeddr Arguments         runnable is bound to the provided port and contains the
                                                                                C implementation to add up both arguments. To instantiate
M2MFrame2Frame annotates a Callable as well, but lifts                          both components, we create an instance configuration
its stack frames to a Callable from the next higher level.                      that connects both instances based on their provided and
Finally, M2MOutlineFrame annotates a generated ASG node                         required ports. Finally, we create a main function that
originating from a Callable for which we outline a stack frame.                 invokes a testcase testing the Adder component. In this test,
   To lift watch variables, we provide two annotations, both                    we initialize both components (initInstances) and invoke
annotate a Watch Provider: M2MWatch2Watch and M2MChild-                         the add operation on the adder instance. Afterwards, we
Watches2Watches. The former lifts watch variables contributed                   validate the result using assertEquals and verify the call
by the annotated Watch Provider to another Watch Provider                       sequence on Logger using assertMock.
from the next higher level. In contrast, the latter lifts child
                                                                                   With MuLDer, debugging support is always built per lan-
values (also contributed by Watch Providers) as top-level
                                                                                guage construct. Hence, a debugger built with this frame-
watch variables to the next higher level.
                                                                                work consists of debugging implementations for different
   To lift watch variable values originating from Value
                                                                                language constructs. In this case study we build debugging
Providers, we provide three different annotations: first,
                                                                                support for the mock component language. Because this
M2MLiftValue refers to a default value lifting rule and is
                                                                                language extends the mbeddr base language (C) and gets
automatically attached by the Program Annotator, second,
                                                                                reduced to mbeddr’s components language, we expect to
M2MGeneratedDelegateToValueProvider is created by the de-
                                                                                have functioning debugging support for language constructs
bugger developer and refers to another Value Provider dele-
                                                                                from these languages. Debugging support for all languages
gating value lifting to it. Finally, M2MGeneratedValueLifter
                                                                                that are used on the source level and intermediate levels is
is also manually created and refers to a Value Transformation
                                                                                a prerequisite of our approach. First, we define debugging
being used to lift the value representation of a generated Value
                                                                                semantics for mock component and sequence step. For
Provider. We have demonstrated in Section III-D a Value
                                                                                other language constructs from the components language
Transformation for unveiling a string literal from a pointer
                                                                                debugging semantics are already defined. mock component
on char. Fig. 6 below shows the transformation rule for
                                                                                extends component, which already implements Value Provider
StringType with a M2MGeneratedValueLifter being attached
                                                                                and Scope Provider, hence, we do not require any additional
to the generated type and referring to our previously created
                                                                                interface implementations. Because sequence steps can be
Value Transformation (liftCharPointer2StringType).
                                                                                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
      Fig. 6. Annotating a generator template with a M2M lifting rule           body in which stepping functionality can be used, we imple-
                                                                                ment Steppable Composite and return the contained body in
                           IV. C ASE S TUDY                                     the required query. statement list comes from mbeddr C
   mbeddr comes with an extension to declare and instanti-                      which already specifies the required interfaces.
ate components and mock components, both illustrated by                            Next, in Fig. 7 below we annotate the transformation rule
the example in Listing 4. We declare in this listing two                        for mocks, describing the program state lifting. First, we
interfaces, ILogger representing a logging service and IAd-                     create fields (used for storing state) that track the number
der for adding up two numbers. Further, we implement a mock                     of failed expectations and overall call counts. Second, we

 1   mock Logger {                                13   component Adder {                                 25   int main() { return test[testAdd]; }
 2     provides ILogger logger                    14     requires ILogger logger                         26   interface ILogger {
 3     sequence {                                 15     provides IAdder adder                           27     void log(string msg, int a, int b)
 4       0:logger.init                            16     void setup() trigger OnInit {                   28   }
 5       1:logger.log                             17       logger.init();                                29   interface IAdder {
 6     }                                          18     }                                               30     int add(int a, int b)
 7   }                                            19     int add(int a, int b) trigger adder.add {       31   }
 8   instance configuration cfg {                 20       logger.log("adding:", a, b);                  32   testcase testAdd {
 9     Adder adder                                21       return a + b;                                 33     initInstances cfg;
10     LoggerMock logger                          22     }                                               34     assertEquals cfg.adder.add(2,2) == 4;
11     connect logger to adder                    23   }                                                 35     assertMock cfg.logger
12   }                                            24                                                     36   }

                                         Listing 4. Illustrating the usage of components and mocks in a unit test


                                                                           23
copy content (COPY_SRCL) from our mock to the component.                         built with our approach use these rules at debug time to
Third, we generate a runnable being used by the generator                        lift program state from a lower level back to the origin
for assertMock to request the number of failed expectations.                     level, incrementally across intermediate levels. Because we
Fourth, we generate at the bottom of the component for                           describe these lifting rules inside transformations, we sup-
each operation of our provided ports a runnable that                             port multiple generators per language and multiple transfor-
inherits the signature and has a trigger being bound to the                      mation rules per language construct. Consider the language
operation and the associated provided port. Stack frames                         extensions for mock components from Section IV, where
for these generated runnables are not lifted, instead, we                        we have described program state lifting from components
generate for each sequence step a statement list being                           back to mock components, ignoring how components are
annotated with an M2MOutlineFrame annotation, outlining a                        further translated towards the target level. Because we specify
stack frame for the sequence step. The specification shown                       debugging behavior between generated and origin level, de-
at the bottom of Fig. 7 configures these stack frames: program                   buggers built with our approach are not affected by changes
counters for outer stack frames are redefined with the current                   in lower level generators, an important requirement in the
node and we associate unwound stack frames with the higher                       extensible language context. While MuLDer can be used to
level sequence step. The statement list we generate                              build debugging support for many extensible languages, the
from sequence steps increments the call count and verifies                       underlying approach has some limitations that we discuss next.
that current and expected call count are equal, if not, we incre-
ment failed expectations. Further, we annotate the generated                     A. Statically Typed Languages
component with an M2MGeneratedDelegateToValueProvider                               To describe the lifting of watch variable values, our ap-
annotation, referring to the Value Transformation shown in                       proach requires variables to be associated with a type
the middle. This transformation constructs a complex-value                       (Value Provider). This is no limitation for mbeddr, because the
with the mock name as top-level value, whereas child values,                     language is statically typed. However, due to this limitation we
content of kind Watch Provider, are lifted from subvalues of                     cannot support dynamically typed languages, e. g., JavaScript.
the current watch variable value.
                                                                                 B. Stage-wise ASG Node Modifications
                                                                                    Output nodes of one transformation cannot be transformed
                                                                                 by the same generator again, because we would lose informa-
                                                                                 tion about code modification this way. Instead, when using our
                                                                                 approach, these output nodes can only be modified by another
                                                                                 code generator being executed afterwards.
                                                                                 C. Performance Overhead
                                                                                    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). Fur-
                                                                                 ther, 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.
                                                                                                     VI. R ELATED W ORK
                                                                                 A. Debuggers for Abstraction-Raising Languages
                                                                                    Renggli et al. [5] describe a source-level debugger for
Fig. 7. Transformation rule for mocks and associated Value Transformation        Helvetia, a tool that allows users to embed DSLs into Smalltalk
                                                                                 host programs, extending Smalltalk with new syntax and
                          V. D ISCUSSION                                         semantics. While Helvetia translates DSL code directly to
   MuLDer is a language-oriented and incremental framework                       Smalltalk, our approach targets multi-stage transformations,
enabling multi-level debugging for extensible languages. By                      reducing code incrementally to a base language. Further,
using the underlying approach, debugger developers specify                       we show program state based on the currently investigated
debugging behavior in two steps. First, they describe debug-                     abstraction level, while the Helvetia debugger shows this
ging semantics of language constructs, e. g., for callables or                   information in terms of the generated Smalltalk code.
variables, second, they annotate transformations with rules                         MPS comes with an extensible Java and a debugger for this
that appear hereby on intermediate level ASGs. Debuggers                         language. While debuggers built with our approach show the



                                                                            24
program state and imitate stepping commands based on the                    Mannadiar and Vangheluwe [10] describe a debugger for a
currently investigated abstraction level, MPS’ Java debugger             language that is used to model mobile applications. Because
shows the target-level call stack and directs each stepping com-         their modeling tool enables tracing across intermediate levels
mand to the target level, not imitating the expected behavior.           created during code generation, they can debug their model by
   We have built a source-level debugger framework for                   inspecting on each intermediate level the currently involved
mbeddr that maps debug information directly between target               model elements. While they provide tracing for intermediate
and source level [6]. While MuLDer enables encapsulated                  representations, we allow interactive debugging on each level.
debugger modules that are not affected by changes in lower
                                                                                       VII. S UMMARY AND F UTURE W ORK
level generators, debuggers built with the mbeddr framework
depend on the structure of the generated target-level code.                 In this paper we have presented an incremental approach
Hence, modifying lower level generators usually implies up-              to build debuggers for extensible languages. While this ap-
dating debuggers that have been built with this framework.               proach enables source-level debugging for language users,
   Mierlo [7] describes a debugging approach for modeling                it additionally allows language engineers to debug programs
languages. With this approach, the modal behavior of a sim-              on intermediate levels to analyze bugs introduced by faulty
ulator for the language is described as a state chart, which             transformation rules. Further, we have illustrated the MuLDer
is extended with debugging information. While this approach              framework, an MPS-based implementation of this approach.
targets modeling languages with a fixed set of language                  We have used this framework to build a multi-level debugger
constructs, our approach targets extensible languages. Further,          for mbeddr and demonstrated in this paper how debugging
the approach presented by Mierlo is used for debugging                   behavior for mock components is implemented.
models on the source-level, while our approach allows multi-                In the future, we plan to use MuLDer to build debuggers
level debugging. Finally, Mierlo requires language engineers to          for other extensible languages and in other workbenches to
describe the executable semantics by using state charts, while           evaluate its genericity. Additionally, we plan to extend the
with our approach debugging semantics of language constructs             specification languages coming with MuLDer for specifying
are described and transformation rules are annotated.                    not only debuggers, but also interpreters to allow multi-level
                                                                         interpretation of extensible language programs.
B. Multi-Level Debuggers
                                                                                                      R EFERENCES
   Florisson [8] describes a multi-level debugger for Cython, a
                                                                          [1] JetBrains, “Meta Programming System,” http://jetbrains.com/mps, 2015.
language allowing users to mix C and Python code in the same              [2] M. Voelter, D. Ratiu, B. Schaetz, and B. Kolb, “Mbeddr: An Extensible
program. Such programs are compiled to C and integrated                       C-based Programming Language and IDE for Embedded Systems,” in
with a Python Application Programming Interface (API) for                     Proceedings of the 3rd Annual Conference on Systems, Programming,
                                                                              and Applications: Software for Humanity, ser. SPLASH ’12. New York,
C, thus being accessible from Python. The resulting C code is                 NY, USA: ACM, 2012, pp. 121–140.
further translated to a CPython extension module, which can               [3] E. Bousse, J. Corley, B. Combemale, J. G. Gray, and B. Baudry,
afterwards be called from regular Python code. While code                     “Supporting efficient and advanced omniscient debugging for xdsmls,”
                                                                              in Proceedings of the 2015 ACM SIGPLAN International Conference
written with Cython interacts with Python code, users cannot                  on Software Language Engineering, SLE 2015, Pittsburgh, PA, USA,
debug it with a Python debugger. For this purpose, Florisson                  October 25-27, 2015, R. F. Paige, D. D. Ruscio, and M. Völter, Eds.
proposes a multi-level debugger that allows users to debug                    ACM, 2015, pp. 137–148.
                                                                          [4] D. Pavletic and S. A. Raza, “Multi-Level Debugging for Extensible
Python, Cython and C code simultaneously. While debugging                     Languages,” Softwaretechnik-Trends, vol. 35, no. 1, 2015.
calls from Python to Cython, the debugger follows control                 [5] L. Renggli, T. Gîrba, and O. Nierstrasz, “Embedding Languages without
flow skipping the C abstraction level, such as calls to the                   Breaking Tools,” in ECOOP 2010 - Object-Oriented Programming,
                                                                              24th European Conference, Maribor, Slovenia, ser. Lecture Notes in
Python interpreter (Python API). Thus, during stepping, the                   Computer Science, vol. 6183. Springer, June 2010, pp. 380–404.
user will see Python code calling Cython code calling C code              [6] D. Pavletic, M. Voelter, S. A. Raza, B. Kolb, and T. Kehrer, “Extensible
and can investigate the program state in terms of the respective              Debugger Framework for Extensible Languages,” in 20th Ada-Europe
                                                                              International Conference on Reliable Software Technologies, Madrid
language. Our approach also covers debugging mixed language                   Spain, June 22-26, 2015, Proceedings, ser. Lecture Notes in Computer
programs, however, we support multiple abstraction levels and                 Science, vol. 9111. Springer, 2015, pp. 33–49.
do not switch between them while performing a stepping                    [7] S. V. Mierlo, “Explicit modelling of model debugging and experi-
                                                                              mentation,” in Proceedings of Doctoral Symposium co-located with
command. Instead, our users switch manually between the                       17th International Conference on Model Driven Engineering Languages
various abstraction levels. Additionally, we target extensible                and Systems (2014), Valencia, Spain, September 30, 2014., ser. CEUR
languages, while Cython, Python and C have a fixed syntax.                    Workshop Proceedings, B. Baudry, Ed., vol. 1321, 2014.
                                                                          [8] M. Florisson, “Multi-Level Debugging for Cython,” 14th Twente Student
   Xia et al. [9] present in their work multi-level debugging, an             Conference on IT, vol. 14, no. 1, Jan. 2011.
approach for automatically detecting bugs in transformation               [9] R. Xia, T. Elmas, S. A. Kamil, A. Fox, and K. Sen, “Multi-level
rules. This approach is based on a set of error checking                      Debugging for Multi-stage, Parallelizing Compilers,” EECS Department,
                                                                              University of California, Berkeley, Tech. Rep., Dec 2012.
algorithms that analyze sequential and parallel aspects of               [10] R. Mannadiar and H. Vangheluwe, “Debugging in Domain-Specific
programs created during transformation. While their approach                  Modelling,” in Software Language Engineering - Third International
automatically detects bugs, we allow users to debug their                     Conference, SLE 2010, Eindhoven, The Netherlands, October 12-13,
                                                                              2010, Revised Selected Papers, ser. Lecture Notes in Computer Science,
program execution on the source level and intermediate levels                 vol. 6563. Berlin, Heidelberg: Springer, 2010, pp. 276–285.
created by transformation rules.



                                                                    25