How to specify features when dealing with textual DSLs

post-thumb

Today I will be discussing how to do specify features when dealing with textual DSLs. There is nothing more soul draining than a feature that takes forever to implement because the scope is not well defined. The problem is that there is no standard way to specify a language without diving into grammars and formal semantics. Grammars and formal semantics are precise, but precision and clarity are complementary concepts, the more you get of one, the less you get of the other. Contrary to standard applications, you cannot just write a use case for a language feature. However, here I’m a proposing a different way that I explain the following paragraphs. I believe that having a standardized way of specifying features is a big time saver for an organization.

Language feature requirements usually look like: “support new keyword to do x”, “add new data type support”. Use cases allow us to describe scenarios. This scenarios is just a way to use the system to attain a certain business goal. I already wrote in another post a good option for the specification of requirements are toy examples. A set of toy examples can replace the set of use case scenarios.

Let us look at an example. The description is: “The language supports user defined functions”, a first scenario can be “Function with no parameters”, the toy example can be:

fct myFunction() = {
	return 0
}

A second scenario can be “Function with 1 parameter”:

fct myFunction(x: Int) = {
	return x
}

Use cases also allow us to describe what to do when things do not go well, these are called extensions. We can also describe an extension as a toy example. Only in this case, we just note the error expected in the toy example. Since the construct we use to specify extensions is the same as before, let us call this also a scenario. For example, let us call our error scenario “Function cannot be recursive”, the toy example could be:

fct myFunction(x: Int) = {
	if (x == 0)
		return x
	else 
		return myFunction(x - 1)
		       ^^^^^^^^^^^^^^^^^ Error: Recursive call of myFunction not allowed
}

Just by using these 3 scenarios we have a much better understanding of what the user defined functions look like, and what are their limitations. I think that writing the language features in this way can simplify both the writing by the product owner, and the understanding of the features on the part of the programmer that implements it.

This method is not complete, there is the semantics that is not defined at all in these examples. However, it is a good start to have at least an informal definition and then the semantics can be given depending on the kind of manipulation that is later done with the DSL files. If code is generated, then we might want to give an example of the usage of the generated code, with some inputs and the corresponding outputs that are expected.

The final part of the post describes how the full requirement would look like.

Full Example

REQ-01: The language supports user-defined functions

This feature enhances the language by adding support for user-defined functions.

Scenario 1: Function with no parameters

fct myFunction() = {
	return 0
}

Scenario 2: Function with 1 parameter

fct myFunction(x: Int) = {
	return x
}

Scenario 3: Function cannot be recursive

fct myFunction(x: Int) = {
	if (x == 0)
		return x
	else 
		return myFunction(x - 1)
		       ^^^^^^^^^^^^^^^^^ Error: Recursive call of myFunction not allowed
}
comments powered by Disqus