=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==
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