Skip to content

Commit ff5caf0

Browse files
author
Badacadabra
committed
Add Interpreter (ES5 + ES6 + CoffeeScript + TypeScript)
1 parent 33929d2 commit ff5caf0

File tree

7 files changed

+412
-0
lines changed

7 files changed

+412
-0
lines changed
2.24 KB
Binary file not shown.
19.4 KB
Loading
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Synopsis
2+
3+
I love music and I want to be able to analyze some compositions like "Moonlight Sonata" by Beethoven.
4+
5+
# Problem
6+
7+
Music is a beautiful but complex universe, and listening to music does not mean understanding it. To analyze compositions, music theory is an absolute necessity. Since music is a language, pretty much like English or French are, it has its own grammar. Understanding this grammar makes it possible to understand a composition. But here the question is: how do we describe a grammar with a programming language?
8+
9+
# Solution
10+
11+
The Interpreter pattern is exactly what we need to tell JavaScript how to interpret music grammar. Here we can have:
12+
13+
* A concrete context to analyze, which will be a sonata (e.g. "Moonlight Sonata")
14+
* An abstract representation of music notation (abstract class or interface)
15+
* Concrete (terminal and nonterminal) expressions like Note or Arpeggio
16+
17+
The distinction between terminal and nonterminal expressions in the Interpreter pattern is a bit like the distinction between simple and composed elements in the Composite pattern. A note is a terminal expression because it is an "elementary symbol" of the grammar. However, an arpeggio is a nonterminal expression because it can be considered as a group of notes.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# ==============================
2+
# CONCRETE CONTEXT
3+
# ==============================
4+
5+
class Sonata
6+
constructor: (@name, @composer) ->
7+
8+
getName: ->
9+
@_name
10+
11+
getComposer: ->
12+
@_composer
13+
14+
# ==============================
15+
# ABSTRACT EXPRESSION
16+
# ==============================
17+
18+
class MusicNotation
19+
constructor: (name) ->
20+
throw new Error "You cannot instantiate an abstract class!" if @constructor is MusicNotation
21+
@_name = name
22+
23+
interpret: (sonata) ->
24+
throw new Error "You cannot call an abstract method!"
25+
26+
# ==============================
27+
# CONCRETE EXPRESSIONS
28+
# ==============================
29+
30+
# Terminal expression
31+
class Note extends MusicNotation
32+
constructor: (name) ->
33+
super name
34+
35+
interpret: (sonata) ->
36+
"#{sonata.composer} played #{@_name}\n"
37+
38+
# Nonterminal expression
39+
class Arpeggio extends MusicNotation
40+
constructor: (name) ->
41+
super name
42+
@_notes = []
43+
44+
addNotes: (newNotes) ->
45+
@_notes.push note for note in newNotes
46+
47+
48+
interpret: (sonata) ->
49+
res = ""
50+
res += note.interpret sonata for note in @_notes
51+
res += "This was a #{@_name} arpeggio from #{sonata.name}.\n"
52+
res
53+
54+
# ==============================
55+
# CLIENT CODE
56+
# ==============================
57+
58+
# To avoid overcomplexity, we only take into account the first measure and the right hand (G-clef)
59+
sonata = new Sonata "Moonlight Sonata", "Beethoven"
60+
61+
# Notes
62+
gSharp = new Note "G#"
63+
cSharp = new Note "C#"
64+
e = new Note "E"
65+
66+
# Arpeggio
67+
cSharpMinor = new Arpeggio "C#m"
68+
cSharpMinor.addNotes [gSharp, cSharp, e]
69+
70+
# Melody
71+
melody = []
72+
73+
melody.push cSharpMinor for i in [1..4] # The same musical pattern is repeated four times...
74+
75+
# Interpretation
76+
console.log arpeggio.interpret sonata for arpeggio in melody
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// ==============================
2+
// CONCRETE CONTEXT
3+
// ==============================
4+
5+
class Sonata {
6+
private name: string;
7+
private composer: string;
8+
9+
constructor(name: string, composer: string) {
10+
this.name = name;
11+
this.composer = composer;
12+
}
13+
14+
public getName(): string {
15+
return this.name;
16+
}
17+
18+
public getComposer(): string {
19+
return this.composer;
20+
}
21+
}
22+
23+
// ==============================
24+
// ABSTRACT EXPRESSION
25+
// ==============================
26+
27+
abstract class MusicNotation {
28+
protected name: string;
29+
30+
constructor(name: string) {
31+
this.name = name;
32+
}
33+
34+
public abstract interpret(sonata: Sonata): string;
35+
}
36+
37+
// ==============================
38+
// CONCRETE EXPRESSIONS
39+
// ==============================
40+
41+
// Terminal expression
42+
class Note extends MusicNotation {
43+
constructor(name: string) {
44+
super(name);
45+
}
46+
47+
public interpret(sonata: Sonata): string {
48+
return `${sonata.getComposer()} played ${this.name}\n`;
49+
}
50+
}
51+
52+
// Nonterminal expression
53+
class Arpeggio extends MusicNotation {
54+
private notes: Note[] = [];
55+
56+
constructor(name: string) {
57+
super(name);
58+
}
59+
60+
public addNotes(newNotes: Note[]): void {
61+
for (let note of newNotes) {
62+
this.notes.push(note);
63+
}
64+
}
65+
66+
public interpret(sonata: Sonata): string {
67+
let res: string = "";
68+
for (let note of this.notes) {
69+
res += note.interpret(sonata);
70+
}
71+
res += `This was a ${this.name} arpeggio from ${sonata.getName()}.\n`;
72+
return res;
73+
}
74+
}
75+
76+
// ==============================
77+
// CLIENT CODE
78+
// ==============================
79+
80+
// To avoid overcomplexity, we only take into account the first measure and the right hand (G-clef)
81+
let sonata: Sonata = new Sonata("Moonlight Sonata", "Beethoven");
82+
83+
// Notes
84+
let gSharp: Note = new Note("G#"),
85+
cSharp: Note = new Note("C#"),
86+
e: Note = new Note("E");
87+
88+
// Arpeggio
89+
let cSharpMinor: Arpeggio = new Arpeggio("C#m");
90+
cSharpMinor.addNotes([gSharp, cSharp, e]);
91+
92+
// Melody
93+
let melody: Arpeggio[] = [];
94+
95+
for (let i = 0; i < 4; i++) { // The same musical pattern is repeated four times...
96+
melody.push(cSharpMinor);
97+
}
98+
99+
// Interpretation
100+
for (let arpeggio of melody) {
101+
console.log(arpeggio.interpret(sonata));
102+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict';
2+
3+
// ==============================
4+
// CONCRETE CONTEXT
5+
// ==============================
6+
7+
var Sonata = (function () {
8+
function Sonata(name, composer) {
9+
this._name = name;
10+
this._composer = composer;
11+
}
12+
13+
Sonata.prototype.getName = function () {
14+
return this._name;
15+
};
16+
17+
Sonata.prototype.getComposer = function () {
18+
return this._composer;
19+
};
20+
21+
return Sonata;
22+
})();
23+
24+
// ==============================
25+
// ABSTRACT EXPRESSION
26+
// ==============================
27+
28+
var MusicNotation = (function() {
29+
function MusicNotation(name) {
30+
if (this.constructor === MusicNotation) {
31+
throw new Error("You cannot instantiate an abstract class!");
32+
}
33+
this._name = name;
34+
}
35+
36+
MusicNotation.prototype.interpret = function (sonata) {
37+
throw new Error("You cannot call an abstract method!");
38+
};
39+
40+
return MusicNotation;
41+
})();
42+
43+
// ==============================
44+
// CONCRETE EXPRESSIONS
45+
// ==============================
46+
47+
// Terminal expression
48+
var Note = (function () {
49+
function Note(name) {
50+
MusicNotation.call(this, name);
51+
}
52+
Note.prototype = Object.create(MusicNotation.prototype);
53+
Note.prototype.constructor = Note;
54+
55+
Note.prototype.interpret = function (sonata) {
56+
return sonata.getComposer() + " played " + this._name + "\n";
57+
};
58+
59+
return Note;
60+
})();
61+
62+
// Nonterminal expression
63+
var Arpeggio = (function () {
64+
var notes = [];
65+
66+
function Arpeggio(name) {
67+
MusicNotation.call(this, name);
68+
}
69+
Arpeggio.prototype = Object.create(MusicNotation.prototype);
70+
Arpeggio.prototype.constructor = Arpeggio;
71+
72+
Arpeggio.prototype.addNotes = function (newNotes) {
73+
for (var i = 0, len = newNotes.length; i < len; i++) {
74+
notes.push(newNotes[i]);
75+
}
76+
};
77+
78+
Arpeggio.prototype.interpret = function (sonata) {
79+
var res = "";
80+
for (var i = 0, len = notes.length; i < len; i++) {
81+
res += notes[i].interpret(sonata);
82+
}
83+
res += "This was a " + this._name + " arpeggio from " + sonata.getName() + ".\n";
84+
return res;
85+
};
86+
87+
return Arpeggio;
88+
})();
89+
90+
// ==============================
91+
// CLIENT CODE
92+
// ==============================
93+
94+
// To avoid overcomplexity, we only take into account the first measure and the right hand (G-clef)
95+
var sonata = new Sonata("Moonlight Sonata", "Beethoven");
96+
97+
// Notes
98+
var gSharp = new Note("G#"),
99+
cSharp = new Note("C#"),
100+
e = new Note("E");
101+
102+
// Arpeggio
103+
var cSharpMinor = new Arpeggio("C#m");
104+
cSharpMinor.addNotes([gSharp, cSharp, e]);
105+
106+
// Melody
107+
var melody = [];
108+
109+
for (var i = 0; i < 4; i++) { // The same musical pattern is repeated four times...
110+
melody.push(cSharpMinor);
111+
}
112+
113+
// Interpretation
114+
for (var j = 0, len = melody.length; j < len; j++) {
115+
console.log(melody[j].interpret(sonata));
116+
}

0 commit comments

Comments
 (0)