Sunteți pe pagina 1din 9

Implementation of a Compiler 

in Haskell for a Simple Music 


Language 
Gabriele Stockunaite GS14623 

Gabriele Stockunaite 
19.12.2016 
Language Engineering  

 
 
 
 
 

INTRODUCTION  

Domain Specific Languages 

A domain-specific language (DSL) is a computer language specialized to a particular 


application domain. This is in contrast to a general-purpose language (GPL), which is 
broadly applicable across domains. Embedded (or internal) domain-specific languages, 
implemented as libraries which exploit the syntax of their host general purpose language 
or a subset thereof, while adding domain-specific language elements (data types, 
routines, methods, macros etc.). 

Haskell as a Host Language  

Haskell is amazing for creating embedded domain-specific languages. In other words, 


before writing your compiler you capture the language you want to compile (the source 
language) in a type. Expressions of that type then represent terms in the source language 
and may actually look quite similar to it, despite being in Haskell. 

Then you represent the target language as another such type. Finally the compiler is 
really a function from the source type to the target type and the translations are easy to 
write and later read. Optimizations are also regular functions that map, say, the target 
language to the target language. 

This is why functional languages with lightweight syntax and a strong type system are 
considered a good option at creating compilers. 

DEEP AND SHALLOW EMBEDDINGS 

Shallow embedding: All Haskell operations immediately translate to the target language. 
E.g. the Haskell expression a+b is translated to a String like "a + b” containing that target 
language expression. 

Deep embedding:  

Haskell operations only build an interim Haskell data structure that reflects the 
expression tree. E.g. the Haskell expression A+b is translated to the Haskell data 

1   
structure Add (Var "a") (Var "b"). This structure allows transformations like 
optimizations before translating to the target language. 

IDEA OF THE MUSIC LANGUAGE 

As a drummer I have used jfugue to java code simple melodies and rithms so I decided to 
make it more approachable so that you could only write : PLAY A, B and it would 
generate all the java code. 

WRITING THE COMPILER 


The grammar above will be used to produce a parser using Megaparsec, 

which is a maintained version of Parsec, a parser combinator library. 

The combinators that are used here are given below: 

pure ​::​ ​Applicative​ f ​=>​ a ​->​ f a

​(<​$​>)​ ​::​ ​Functor​ f ​=>​ ​(​a ​->​ b​)​ ​->​ f a ​->​ f b

​(<​$​)​ ​::​ ​Functor​ f ​=>​ a ​->​ f b ​->​ f a

​(<*>)​ ​::​ ​Applicative​ f ​=>​ f ​(​a ​->​ b​)​ ​->​ f a ​->​ f b

​(<*)​ ​::​ ​Applicative​ f ​=>​ f a ​->​ f b ​->​ f a

​(*>)​ ​::​ ​Applicative​ f ​=>​ f a ​->​ f b ​->​ f b

​(<|>)​ ​::​ ​Alternative​ f ​=>​ f a ​->​ f a ​->​ f a

many ​::​ ​Alternate​ f ​=>​ f a ​->​ f ​[​a]

some ​::​ ​Alternate​ f ​=>​ f a ​->​ f ​[​a]

sepBy ​::​ ​Alternative​ f ​=>​ f a ​->​ f sep ​->​ f ​[​a]

sepBy1 ​::​ ​Alternative​ f ​=>​ f a ​->​ f sep ​->​ f ​[​a]

2   
oneOf ​::​ ​[​a​]​ ​->​ ​Parser​ ​Char

noneOf ​::​ ​[​a​]​ ​->​ ​Parser​ ​Char

Notice that most of these do not mention `Parser` directly; a `Parser` 

is in fact an instance of `Functor`, `Applicative`, `Alternative`, and 

`Monad`: these concepts generalise to all kinds of other applications. The copiler is 
based on the example provided in Bristol Tiny Basic code. 

RESULTS

{-​# LANGUAGE StandaloneDeriving #-}

module​ ​MusicBasic​ ​where

import​ ​Text​.​Megaparsec

import​ ​Text​.​Megaparsec​.​String

import​ ​Data​.​List​ ​(​intercalate)

data ​Prog​ ​=​ ​Prog​ ​[​Line]

data ​Line​ ​=​ ​Cmnd​ ​Cmnd​ ​|​ ​Stmt​ ​Stmt​ ​|​ ​Line​ ​Int​ ​Stmt

data ​Cmnd​ ​=​ CLEAR ​|​ LIST ​|​ RUN ​|​ ​BEGIN​ ​|​ FINISH

data ​Stmt​ ​=​ PRINT ​Args

|​ PLAYNOTES ​[​Note]

|​ PLAYSOUNDS ​Mexpr

|​ LET ​Ident​ ​Expr

|​ RETURN

|​ RYTHM ​Number

data ​Dur =​ W ​|​ H ​|​ Q

data ​Chord​ ​=​ maj ​|​ minor ​|​ aug ​|​ dim

3   
type ​Note =​ ​Char

type ​Sound​ ​=​ ​Note​ ​|​ ​Note​ ​Dur​ ​|​ ​Note​ ​Chord​ ​|​ ​Note​ ​Chord​ ​Dur

type ​Args =​ ​[​Either​ ​String​ ​Mexpr]

data ​Instrument​ ​=​ PIANO ​|​ VIOLIN ​|​ FLUTE

data ​Mexpr​ ​=​ ​Instrument​ ​[​Sounds]

type ​Ident​ ​=​ ​Char

prog ​::​ ​Parser​ ​Prog

prog ​=​ ​Prog​ ​<​$​>​ many line

line ​::​ ​Parser​ ​Line

line ​=​ ​Cmnd​ ​<​$​>​ cmnd ​<*​ cr

<|> Stmt <$> stmt <* cr

<|> Line <$> number <*> stmt <* cr

cmnd ​::​ ​Parser​ ​Cmnd

cmnd ​=​ CLEAR ​<​$ tok ​"CLEAR"

<|> LIST <$ tok "LIST"

<|> RUN <$ tok "RUN"

<|> BEGIN <$ tok "BEGIN"

<|> FINISH <$ tok "FINISH"

stmt ​::​ ​Parser​ ​Stmt

stmt ​=​ PRINT ​<​$ tok ​"PRINT"​ ​<*>​ args

<|> PLAYNOTES <$ tok "PLAYNOTES" <*> notes

<|> PLAYSOUND <$ tok "PLAYSOUNDS" <*> mexpr

4   
<|> LET <$ tok "LET" <*> var <* tok "=" <*> expr

<|> GOSUB <$ tok "GOSUB" <*> expr

<|> RETURN <$ tok "RETURN"

<|> END <$ tok "END"

​--​ ​<|>​ ​BEGIN​ ​<​$ tok ​"BEGIN"

​--​ ​<|>​ PLAYNOTES ​<​$ tok ​"PLAYNOTES"

args ​::​ ​Parser​ ​Args

args ​=​ sepBy1 ​((​Left​ ​<​$​>​ str​)​ ​<|>​ ​(​Right​ ​<​$​>​ expr​))​ ​(​tok ​",")

str ​::​ ​Parser​ ​String

str ​=​ tok ​"\""​ ​*>​ some ​(​noneOf ​(​"\n\r\""​))​ ​<*​ tok ​"\""

expr ​::​ ​Parser​ ​Expr

expr ​=​ ​Expr​ ​<​$​>​ ​((​POS ​<​$ tok ​"+"​)​ ​<|>​ ​(​NEG ​<​$ tok ​"-"​)​ ​<|>​ pure POS)

<*> term <*> many exprs

mexpr ​::​ ​Parser​ ​Mexpr

mexpr ​=​ ​Mexpr​ ​<​$​>​ ​((​PIANO ​<​$ tok ​"Piano"​)​ ​<|>​ ​(​VIOLIN ​<​$ tok ​"Violin"​)​ ​<|>​ ​(​FLUTE ​<​$ tok
"Flute"​))

<*> sounds

notes ​::​ ​Parser​ ​[​Note]

notes ​=​ sepBy1 note ​(​tok ​",")

sounds ​::​ ​Parser​ ​[​Sound]

5   
sounds ​=​ sepBy1 sound ​(​tok ​",")

sound ​::​ ​Parser​ ​Sound

sound ​=​ note

<|> ((W <$ tok "w") <|> (H <$ tok "h") <|> (Q <$ tok "q")) <*> note

<|> note <*> chord

<|> ((W <$ tok "w") <|> (H <$ tok "h") <|> (Q <$ tok "q")) <*> note <*> chord

note ​::​ ​Parser​ ​Char

note ​=​ oneOf ​[​'A'​,​ ​'B'​,​ ​'C'​,​ ​'D'​ ​,​'E'​ ​,​ ​'F'​,​ ​'G'​ ​]​ ​<*​ whitespace

var​ ​::​ ​Parser​ ​Char

var​ ​=​ oneOf ​[​'a'​ ​..​ ​'z'​]​ ​<*​ whitespace

vars ​::​ ​Parser​ ​[​Ident]

vars ​=​ sepBy1 ​var​ ​(​tok ​",")

number ​::​ ​Parser​ ​Int

number ​=​ ​(​some ​(​oneOf ​[​'0'​ ​..​ ​'9'​])​ ​>>=​ ​return​ ​.​ read​)​ ​<*​ whitespace

chord ​::​ ​Parser​ ​Chord

chord ​=​ maj ​<​$ tok ​"maj"

<|> minor <$ tok "minor"

<|> aug <$ tok "aug"

<|> dim <$ tok "dim"

6   
parseFile ​::​ ​FilePath​ ​->​ IO ​()

parseFile filePath ​=​ ​do

file ​<-​ readFile filePath

writeFile ​"Music.java"​ $ ​case​ parse prog filePath file of

Left​ err ​->​ parseErrorPretty err

Right​ prog ​->​ pretty prog

Then the actual translation becomes easy when we have all the language parsed for example
begining the code.

instance ​Pretty​ ​Cmnd​ ​where

pretty ​(​BEGIN​)​ ​=​ ​"import org.jfugue.pattern.Pattern;"​ ​++​ ​"\r\n"​ ​++​ ​"import


org.jfugue.player.Player;"​ ​++​ ​"\r\n"​ ​++​ ​"import org.jfugue.theory.Chord;"​ ​++​ ​"\r\n"​ ​++​"import
org.jfugue.theory.ChordProgression;"​ ​++​ ​"\r\n"​ ​++​ ​"import org.jfugue.theory.Note;"​ ​++
"\r\n"​++​"import org.jfugue.rhythm.Rhythm;"​ ​++​ ​"\r\n"​ ​++​ ​"public class Music {"​ ​++​ ​"\r\n"​ ​++
"public static void main(String[] args) {"

pretty ​(​FINISH​)​ ​=​ ​" }}"

REFERENCES 

https://www.reddit.com/r/haskell/comments/21x8br/tutorial_for_building_a_compiler_in_
haskell/ 

https://wiki.haskell.org/Embedded_domain_specific_language 

http://augustss.blogspot.co.uk/search/label/BASIC 

http://dev.stephendiehl.com/fun/ 

https://en.wikipedia.org/wiki/Domain-specific_language 

7   
https://www.reddit.com/r/haskell/comments/2e8d53/whats_the_best_practice_for_buildin
g_a_dsl_in/ 

https://wiki.haskell.org/Parsing_a_simple_imperative_language 

8   

S-ar putea să vă placă și