Domain-specific languages have become a hot topic; Many functional languages are popular, mainly because they can be used to build domain-specific languages. For this reason, in the 8th article in the Scala Guide series for Java™ developers, Ted Neward to build a simple calculator DSL to demonstrate the power of building an "external" DSL for functional languages. He examines a new feature of Scala: Case classes, and re-examine a powerful feature: pattern matching.
After last month's article, I received some complaints/comments that the examples I have used so far in this series do not involve any substantive issues. Of course it is reasonable to use some small examples in the early days of learning a new language, and it is also natural that readers want to see some more "realistic" examples to understand the deep realms and powerful features of language and their advantages. So in this month's article, we'll do a two-part practice of building domain-specific languages (DSL)-This is an example of a small calculator language.
Domain-specific language
Maybe you can't (or don't have the time) to bear the pressure from your project manager, so let me say it directly: the domain-specific language is simply to try (again) to put an application's functionality where it belongs-to the user's hands.
By defining a text language that a new user can understand and use directly, the programmer succeeds in getting rid of the hassle of constantly processing UI requests and enhancements, and also allows users to create scripts and other tools themselves to create new behaviors for the applications they build. While this example may be a bit risky (and may provoke a few complaints), I would say that the most successful example of DSL is Microsoft®office Excel "language", which is used to express the various calculations and contents of spreadsheet cells. Even some people think that SQL itself is a DSL, but this time it is a language designed to interact with relational databases (imagine what it would be like if a programmer were to get data from Oracle using the traditional API read ()/write).
The DSL built here is a simple calculator language used to get and compute mathematical expressions. In fact, the goal here is to create a small language that allows the user to enter a relatively simple algebraic expression, and then the code evaluates it and produces results. To be as simple and straightforward as possible, the language does not support the features supported by many functional calculators, but I don't want to limit its usefulness to teaching-the language must be extensible enough to enable the reader to use it as the core of a more powerful language without having to change the language completely. This means that the language must be easily expanded and kept as encapsulated as possible without any hindrance.
In other words, the goal is to allow the client to write code to achieve the following objectives:
Listing 1. Calculator DSL: Target
// This is Java using the Calculator
String s = "((5 * 10) + 7)";
double result = com.tedneward.calcdsl.Calculator.evaluate(s);
System.out.println("We got " + result); // Should be 57
We won't finish all the discussion in an article, but we can learn a bit about it in this article and complete it in the next post.
From an implementation and design point of view, it is tempting to build a parser that can "pick each character and compute dynamically" from a string-based parser, but this is only true for simpler languages and is not very extensible. If the goal of language is to achieve simple extensibility, before delving into the implementation, let's take a moment to think about how to design the language.
Based on the most essential part of the basic compilation theory, you can learn that the basic operations of a language processor (including interpreters and compilers) consist of at least two phases:
Parser that gets the input text and converts it to an Abstract Syntax tree (AST).
A code generator (in the case of a compiler) that gets the AST and generates the desired bytecode from it, or the evaluation (in the case of an interpreter), which is used to fetch the AST and compute what it finds in the AST.
Owning an AST can optimize the result tree to some extent, if you realize this, then the reason for the difference becomes more obvious; for calculators, we might want to examine the expression carefully to find out where the entire fragment of the expression can be truncated, such as the number of operands in the multiplication expression is "0" Position (it shows that the result of the operation will be "0" regardless of the number of other shipping counts).
The first thing you want to do is define the AST for the calculator language. Fortunately, Scala has case classes: A class that provides rich data, uses a very thin package, and has features that make them ideal for building the AST.