skip to content
An anime girl with glasses

An Alternative to Switch Cases in Rascal

/ 2 min read

An Alternative to Switch Cases in Rascal

The Problem

I’m currently writing some mapping code from an internal Rascal - DSL to JSON (in order to parse that later on in Java 😮‍💨). In theory I’m “simply” traversing a tree and every time I encounter a node of a certain type (defined below), I apply some mapping function to JSON code (creating the JSON string).

/* Here I'm defining a data type with different constructors
You can think of it as if RavenNode would be the class "Animal" and each 
function-alike constructor a certain type of animal (e.g. cat(str name, str breed))
*/ 
public data RavenNode = 
            ravenNode2D(str nodeID, list[RavenNode] children)
            | ravenButton(str nodeID,str label)
            | ravenLabel(str nodeID, str text);
            

First idea was to write some Switch statements like this:


str node2DtoJSON(RavenNode node2D)  = "
    \"id\": <rvn_print(node2D.nodeID)>
    <if(children!=[]){>,\"children\":<rvn_print(node2D.children)<}> 
    ";


void traverseTree(NodeType root) {  
    switch (root) {
    case ravenNode2D(_, _):  node2DtoJSON(root);
    case ravenLabel(_,_):   labelToJSON(root);
    ...
}
... 
}

But I have a lot of cases and it would become a bit unreadable at some point (at least for me). Luckily, Rascal provides us with a “cleaner” way to do that with pattern matching.

The Solution

In which you can define the following:

public str toString(x) = rvn_print(x);
str rvn_print(int number) = "<number>";
str rvn_print(str string) =  "\"<string>\"";
str rvn_print(list[RavenNode] children: []) = "";

/* The pattern matching can be seen here, you define the type and after the ':'
you define the pattern Rascal should match to. Here it is the type "ravenLabel(...)"*/
str rvn_print(RavenNode nodeName: ravenLabel(str nodeID, str text)) {
    return "\"label<rvn_print(uuidi())>\": {
    '   \"id\": <rvn_print(nodeID)>,
    '   \"text\": <rvn_print(text)>
    '}  ";
}
/* Here the type 'ravenNode2D(...) is matched */
str rvn_print(RavenNode nodeName:ravenNode2D(str nodeID, list[RavenNode] children)) = "
    \"id\": <rvn_print(nodeName.nodeID)>
    <if(children!=[]){>,\"children\":<rvn_print(children)><}> 
    ";

/* The main (simplified) function to do a JSON mapping */ 

RavenNode createJSONString(RavenNode tree) =  top-down-break visit(tree){      
    case RavenNode tree : JSON_CONTENT += rvn_print(tree);
};

void main(RavenNode tree) {
    createJSONString(tree);
    writeFile(JSON_TREE_FILE, JSON_CONTENT);
}

As you can see, you only have to call one case now and Rascal matches the type and applies the correct mapping function (which is utilising the StringTemplate feature of Rascal).

If this is cleaner or not is in the eye of the beholder, but I believe it is a worthy alternative to mention 😊.

Further Resources