• No results found

Improving the JSON Parser

In document Practical Scala DSLs (Page 117-122)

To make the result of the parser more humanly readable, we have to change the result of the parser a little. The most useful structure we can use is a simple Scala Map, in which the key is the name of the JSON property, and the value is the associated value.

The first step of the parser is now to create a Map when we parse the entire JSON object. The new code for the function looks like the following:

def objson: Parser[Map[String, Any]] =

"{"~> repsep(member, ",") <~"}" ^^ (Map() ++ _)

You can see that we’ve changed the signature of the method. First, we use the value Any. This means that we return any value from our parser.

In this case, the parser returns a value Map[String, Any]. This tells the parser to cast the result into a map of value string and any.

To make this transformation, we use a new parser operator: ^^. This operator transforms the result of the parser operation. To use this operator, the parser must have the syntax P ^^ r. In our case, we use a repetition control on the member, which means that we have a result transformed in a case class, and this initiates the transformation.

At the same time, we change another method to improve the parser.

We change the member, as follows:

def member: Parser[(String, Any)] = stringLiteral~":"~value ^^

{ case Name~":"~value => (Name, value) }

For the other method, we use the operator ^^ to transform the result of the operation. In this case, we use a pattern matching to select the value Name. The entire parser code now looks like this:

import scala.util.parsing.combinator._

class ImprovedJsonParser extends JavaTokenParsers {

109 def obj: Parser[Map[String, Any]] =

"{"~> repsep(member, ",") <~"}" ^^ (Map() ++ _) def array: Parser[List[Any]] =

"["~> repsep(value, ",") <~"]"

def member: Parser[(String, Any)] = stringLiteral~":"~value ^^

{ case name~":"~value => (name, value) } def value: Parser[Any] = (

obj | array

| stringLiteral

| floatingPointNumber ^^ (_.toDouble) )

}

object ImprovedJsonParserTest extends ImprovedJsonParser { def main(args: Array[String]) {

val reader = "{\n\t\"Username\" : " + "[{\"Name\" : \"Pierluigi Riti\"," +

"\"Roles\" : [\"Administrator\", \"User\"]," + "\"Groups\" : [\"Test1\",\"Test2\"]," +

"\"Permissions\": [\"All\", \"Read\"]" + "}," +

"{\"Name\" : \"John Smyth\"," + "\"Roles\" : [\"User\"]," + "\"Groups\" : [\"Test1\"]," + "\"Permissions\": [\"Read\"]}]} "

println(parseAll(value, reader)) }

}

110

If we execute the code, we actually have a result like the following:

Map(

We can see the result is now more readable to humans than the previous result. We finally realize that our own parser is using an external DSL to define it. When we create an external DSL, we have a set of patterns that we can use to generate a parser. We have, for example, a parser

generator pattern, and this is exactly what we use here. This pattern uses an external grammar to generate the language. An external DSL essentially generates a code used as an external source. In this case, we use a JSON file. A JSON file offers us the grammar we need to read and parse to generate the code.

We can use different techniques for parsing the file. In this case, I used a short introduction and a JSON, but we can create more complex parsers using DSLs. For example, we can create a syntax-directed translation. This pattern translates a source, normally a text, by defining a grammar and then using this grammar to create a structured translation. We see the use

111 of this pattern, for example, when we translate the EBNF into a software.

We take a grammar, the EBNF, and then generate an input, based on the grammar. The software gets the grammar and then translates the source into software.

This parse uses a JSON format notation, which allows us to create our grammar. This is because it is easy to parse and create our own syntax for the software we want define.

What we have learned until now is sufficient to start to create our own simple parser, but we need to know what element we used to create the parser (see Table 6-2).

Table 6-2. Parser Operator Used to Create Our Simple JSON Parser

Operator Description

… Literal

“…”.r regular expression

p~Q sequence of composition

p<~Q , p~>Q sequence of composition: follow only move left/right p | Q alternative: the result is p or Q

opt(p) optional: in case of success p rep(p) repeat the parser on p repsep(p,Q) interleaved repetition p ^^ f result of the conversion

In the preceding table, we see how many combinator parsers use symbolic names to describe an operation, for example, <~ or ^^.

This notation has the big advantage of being compact to write but, on the other hand, is difficult to remember and can be very cryptic for people with little experience. The big advantage of using a symbolic name is the short length of the code and the possibility of implementing the correct precedence in the parser itself.

112

What we have created until now is an in-memory parser. We can use this parser to develop a sequence call of methods to execute operations on the system. For example, we can create the function and use it to update a table in a database or file system.

Conclusion

In this chapter, you received a brief introduction to the Scala’s Parser Combinator Library. This library is very powerful and can be used to create equally powerful code.

We scratched only the surface of the Parser Combinator Library, but in spite of that, we were able to create a powerful parser to read and create in memory three others with the structure of the code.

In the next chapter, you will see how to a create more complex parser, when I cover the rest of Scala’s Parsing Combinator Library.

113

© Pierluigi Riti 2018

P. Riti, Practical Scala DSLs, https://doi.org/10.1007/978-1-4842-3036-7_7

Creating a Custom

In document Practical Scala DSLs (Page 117-122)

Related documents