List of Figures
Chapter 1. Learning to speak the language of the domain
Figure 1.1. Entities and collaborations from the problem domain must map to appropriate artifacts in a solution domain. The entities shown on the left (security, trade, and so on) need corresponding representations on the right.
Figure 1.2. The problem domain and the solution domain need to share a common vocabulary for ease of communication. With this vocabulary, you can trace an artifact of the problem domain to its appropriate representation in the solution domain.
Figure 1.3. A DSL script provides a representation of the domain language to the implementation model. It uses the common vocabulary as the underlying dictionary that makes the language feel more natural to users.
Figure 1.4. Three execution models for a DSL script. You can directly execute the program that implements the solution domain model . Alternatively you can instrument bytecodes and then execute the script . Or you can do a source code translation (as with Lisp macros) and then generate bytecodes for execution .
Figure 1.6. You need to develop your own language-processing infrastructure for an external DSL. The infrastructure includes lexical analyzers, parsers, and code generators commonly found in high-level language implementations. Note that the complexities of each of them depend on how detailed your language is.
Chapter 2. The DSL in the wild
Figure 2.4. An embedded typed DSL comes with lots of implicit guarantees of consistency. Use a type to model your DSL abstraction. The constraints that you define within your type are automatically checked by the compiler, even before the program runs.
Figure 2.7. You use macros to do compile-time metaprogramming. Your DSL script has some valid language forms and some custom syntax that you’ve defined. The custom syntax is in the form of macros, which get expanded during the macroexpansion phase into valid language forms. These forms are then forwarded to the compiler.
Figure 2.10. XML is being used as the external DSL to abstract Spring configuration specification. The container reads and processes XML during startup and produces the ApplicationContext that your application uses.
Chapter 3. DSL-driven application development
Figure 3.3. Our application architect is having a nightmarish time thinking about how to integrate DSLs written in various languages with the core application. It’s a time bomb that’s waiting to explode. Can you help him out?
Figure 3.5. Integrating the Groovy DSL through the Java 6 scripting engine. The interaction diagram shows all the steps involved in evaluating the Groovy DSL script within the sandbox of the ScriptEngine.
Chapter 4. Internal DSL implementation patterns
Figure 4.2. Internal DSL implementation patterns, along with example artifacts. I’ll discuss each of these artifacts in this chapter, and provide sample implementations in the languages specified in the figure.
Figure 4.3. The steps of pattern application: The account is created through the implicit context that is set up using instance_eval. The account is saved. The Mailer is set up using fluent interfaces and gets the account from a block.
Figure 4.4. How the super call wires up the value() method. The call starts with Commission.value(), Commission being the last module in the chain, and propagates downward until it reaches the Trade class. Follow the solid arrow for the chain. Evaluation follows the dotted arrows, which ultimately results in 220, the final value.
Figure 4.8. Sample view of the account activity report sorted and grouped by the instruments traded during the day. Note how the instruments are sorted and the quantities grouped together under each instrument.
Figure 4.10. Activity report computation grouped by instrument (groupBy(_.instrument)). Follow the steps in the figure and correlate them with listing 4.10 and the snippet that follows it, which uses the DSL to compute the ActivityReport for “john doe”.
Figure 4.13. Compile-time metaprogramming generates code through macro expansion. Note that we’re still in the compilation phase when the code is generated. This technique doesn’t have any runtime overhead, unlike the earlier one in figure 4.12.
Chapter 5. Internal DSL design in Ruby, Groovy, and Clojure
Figure 5.4. How we’ll enrich our Ruby DSL to implement trade processing. At every stage, we’ll make the DSL richer by using the abstraction capability that Ruby offers and add more domain functionality.
Figure 5.10. DSL script to execution model for Clojure. Pay attention to the series of steps that the DSL script goes through before it’s ready for execution. As we discussed in chapter 1, the semantic model bridges the DSL script and the execution model.
Chapter 6. Internal DSL design in Scala
Figure 6.3. With Scala you can use the dual power of OO and functional programming to evolve your domain model. Using the OO features of Scala, you can abstract over types and values, specialize a component through subtyping, and do composition using mixins. You can also use the functional features of Scala through its higher-order functions, closures, and combinators. Finally, you can compose all of this using modules and get your final abstraction.
Figure 6.4. Sequence of implicit conversions that leads to the construction of the FixedIncomeTrade instance. Read the figure from left to right and follow the arrows for implicit conversions and the subsequent creation of helper objects.
Figure 6.6. TradeDSL has an abstract type member T <: Trade, but EquityTradeDSL has the concrete type T = EquityTrade and FixedIncomeTradeDSL has the concrete type T = FixedIncomeTrade. TradeDSL has two specializations in EquityTradeDSL and FixedIncomeTradeDSL.
Chapter 7. External DSL implementation artifacts
Figure 7.2. The simplest form of an external DSL. The parsing infrastructure does everything necessary to produce the target actions. The phases of processing the DSL script (lexicalization, parsing, generating the AST, and code generation) are all bundled in one monolithic block.
Figure 7.4. We’ve split the parsing infrastructure box of figure 7.2 into two separate abstractions. The parser takes care of the core parsing of the syntax. The semantic model is now a separate abstraction from the parsing engine. It encapsulates all the domain concerns that are ready to be fed into the machinery that generates all the target actions.
Figure 7.5. The semantic model evolves bottom-up as a composition of smaller domain abstractions. You develop smaller abstractions for domain entities, as indicated by the dotted rectangles. You then compose them together to form larger entities. In the end, you have the entire domain abstracted in your semantic model.
Figure 7.7. The parser generator takes the grammar rules and the custom actions as input. It then generates the lexical analyzer and the parser, which accept the DSL script and generate the semantic model. Then you can integrate this model with the core application.
Figure 7.10. Hierarchical, or outline view of the model. The outline view shows the structure associated with each rule. You can sort the elements alphabetically and select an element to navigate to the corresponding one in the text editor.
Figure 7.12. The Xtext metamodel provides you with an excellent editor for writing DSLs. See how the code completion suggests alternatives for you? You can also see the syntax highlighting feature, which is quite handy.
Chapter 8. Designing external DSLs using Scala parser combinators
Figure 8.2. Chaining parsers. Parser #1 parses a part of the input stream. Parser #2 matches the part left over by parser #1. The combination returns a parser that combines the two results. The combination parser succeeds only if both parser #1 and parser #2 match their inputs.
Figure 8.3. Implementation architecture of designing an external DSL using an external parser generator like ANTLR. The generator produces a parser that parses the DSL script and generates the semantic model of the application.
Figure 8.4. Implementation architecture of an external DSL designed using parser combinators. You’re completely within the confines of the host language infrastructure when you define grammar rules and custom actions.
Figure 8.8. The parse tree that’s generated from the parsing process of the grammar in listing 8.2. The dotted portion represents repetition of line_item, which ultimately reduces to the order node of the tree.
Figure 8.9. A detailed run-down of how a sample grammar rule returns a Parser[Order]. items and account_spec each flow in as a Parser , to the rule. The sequence combinator runs Parser[Items] and Parser[AccountSpec] and passes the results as an instance of ~ to the function application combinator . Pattern matching is done and an Order instance is created . Through an implicit conversion, Order is lifted into a Parser and is returned .
Figure 8.10. The trade and settlement processes. Trade is a promise made between two counterparties for exchange of securities and cash. The settlement is the actual commitment that transfers securities and cash between the counterparty accounts to change positions.
Figure 8.11. The SSIs are needed to complete the process of settlement. The brokers and the custodians need to know the bank and account information where the securities and cash need to be transferred.
Chapter 9. DSL design: looking forward
Figure 9.4. DSL workbenches support the full lifecycle of a DSL implementation. Domain experts work with higher-level structures like Microsoft Excel. The workbench stores metadata instead of program text. The metadata can be projected onto smart editors called projectional editors where you can edit, version, and manage it. The workbenches also have the facility to generate code to programming languages like Java.
Appendix A. Role of abstractions in domain modeling
Appendix B. Metaprogramming and DSL design
Appendix G. Polyglot development