diff --git a/.gitignore b/.gitignore
index 9455005..759bc5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ drafts.cc
.cache
release_*
*.zip
+*.html
diff --git a/doc/musique-vs-languages-cheatsheet.template b/doc/musique-vs-languages-cheatsheet.template
new file mode 100644
index 0000000..c829742
--- /dev/null
+++ b/doc/musique-vs-languages-cheatsheet.template
@@ -0,0 +1,121 @@
+TITLE Porównanie Musique z typowymi językami programowania
+
+BEGIN CSS
+table, tr, td {
+ font-size: 12pt;
+ border: 1pt solid #DDDDDD;
+ border-collapse: collapse;
+ vertical-align: top;
+}
+
+td, th {
+ padding: 2px;
+}
+
+END CSS
+
+BEGIN INTRO
+
Ściągawka Musique, a Python i Ruby
+Ponieważ Musique jest kierowany do osób posiadających doświadczenie z muzyką algorytmiczną, szybkim sposobem na podstawowe poznanie języka jest porównanie z innymi technologiami w tej dziedzinie. Ten dokument służy bardziej ukazaniu różnic niż omówieniu samego języka.
+END INTRO
+
+BEGIN TABLE
+
+n Kategoria
+m Musique
+p Python
+r Ruby (SonicPi)
+c Komentarz
+
+n Deklaracja
+m x := 0
+p x = 0
+r x = 0
+c Zmienne należy zadeklarować by móc z nich korzystać
+
+n Aktualizacja
+m x = 1
+r x = 1
+p x = 1
+
+n Operacje matematyczne
+m x = 10 * 30 - 40 ** 2
+p x = 10 * 30 - 40 ** 2
+r x = 10 * 30 - 40 ** 2
+c Zarówno Python, Ruby jak i Musique posiadają operator potęgi **
+
+n Funkcja anonimowa
+m add := [x y | x + y]
+p add = lambda x, y: x + y
+r add = ->(x, y) { x + y }
+
+n Deklaracja funkcji
+m add := [x y |
+m say x y;
+m x + y
+m ]
+p def add(x, y):
+p print(x, y)
+p return x + y
+r def add(x, y)
+r puts x, y
+r return x + y
+r end
+c Musique nie rozróżnia funkcji anonimowych i zadeklarowanych
+
+n Tablice
+m x = [1; 2; 3; 4]
+p x = [1, 2, 3, 4]
+r x = [1, 2, 3, 4]
+
+n Nty element tablicy
+m x.n
+p x[n]
+r x[n]
+
+n Aktualizacja ntego elementu tablicy
+m x = update x n 10
+p x[n] = 10
+r x[n] = 10
+
+n Tablica od 0 do 9 włącznie
+m x = up 10
+p x = range(10)
+r x = [*0..9]
+
+n Tablica od 1 do 10 włącznie
+m x = 1 + up 10
+m lub x = range 1 11
+p x = range(1, 11)
+r x = [*1..10]
+
+n Iloczyn elementów tablicy
+m fold '* (1 + up 5)
+p functools.reduce(
+p operator.mul, range(1, 6), 1)
+r [*1..5].inject(:*)
+c Musique pozwala zmienić dowolny operator w funkcję
+c poprzez zapis 'operator jak '* w przykładzie
+
+n Instrukcja warunkowa
+m if (n == 42)
+m [ say 1 ]
+m [ say 2 ]
+p if n == 42:
+p print(1)
+p else:
+p print(2)
+r if n == 42
+r puts 1
+r else
+r puts 2
+r end
+c Musique nie posiada instrukcji warunkowej, a funkcję if,
+c która przyjmuje wartość logiczną i dwa bloki (funkcje anonimowe)
+c - jeden z nich zostanie wykonany jeśli warunek jest prawdziwy,
+c a drugi jeśli jest fałszywy.
+n Wyrażenie warunkowe
+m x := if (n == 42) [1] [2]
+p x = 1 if n == 42 else 2
+r x = n == 42 ? 1 : 2
+END TABLE
diff --git a/scripts/language-cmp-cheatsheet.py b/scripts/language-cmp-cheatsheet.py
new file mode 100644
index 0000000..d53ceec
--- /dev/null
+++ b/scripts/language-cmp-cheatsheet.py
@@ -0,0 +1,140 @@
+import argparse
+import os
+import string
+import collections
+
+Directive = collections.namedtuple("Directive", "line_number type content")
+
+HTML_TEMPLATE = string.Template("""
+
+
+
+ $title
+
+
+
+ $intro
+
+
+
+""")
+
+def parse_table(lines: list):
+ previus_column_type = None
+ rows, order = [{}], []
+ current_row = rows[0]
+
+ # Each nonblank matches this regular expression /(\S*)\s(.*)/
+ # where first capture is type of column (essentialy column id) and
+ # second capture is given cell content. Where column type repeats not in row
+ # we have new row.
+ for line in lines:
+ if not line:
+ continue
+
+ sep = line.find(' ')
+ column_type, cell_content = line[:sep].strip(), line[sep:]
+
+ if column_type not in order:
+ order.append(column_type)
+
+ if previus_column_type != column_type and column_type in current_row:
+ rows.append({})
+ current_row = rows[-1]
+
+ cell = current_row.get(column_type, [])
+ cell.append(cell_content)
+ current_row[column_type] = cell
+ previus_column_type = column_type
+
+ # Eliminate common whitespace prefix in given column type (not all whitespace
+ # prefix since examples in Python may have significant whitespace)
+ for row in rows:
+ for cell in row.values():
+ prefix_whitespace = min(len(s) - len(s.lstrip()) for s in cell)
+ for i, s in enumerate(cell):
+ cell[i] = s[prefix_whitespace:]
+
+ return rows, order
+
+def compile_template(*, template_path: str, target_path: str):
+ # Read template file and separate it into lines
+ with open(template_path) as f:
+ template = [line.strip() for line in f.readlines()]
+
+ directives = []
+ for i, line in enumerate(template):
+ s = line.split()
+ if s and s[0] in ("BEGIN", "END", "TITLE"):
+ directives.append(Directive(i, s[0], line[len(s[0]):].strip()))
+
+ title, css_source, table_source, intro_source = 4 * [None]
+
+ for directive in directives:
+ if directive.type == "TITLE":
+ title = directive.content
+ continue
+
+ if directive.type == "BEGIN":
+ start_line = directive.line_number
+ for end in directives:
+ if end.type == "END" and end.content == directive.content:
+ end_line = end.line_number
+ break
+ else:
+ assert False, "Begin without matching end"
+
+ span = template[start_line+1:end_line]
+
+ if directive.content == "CSS":
+ css_source = span
+ elif directive.content == "TABLE":
+ table_source = span
+ elif directive.content == "INTRO":
+ intro_source = span
+
+ assert css_source is not None
+ assert table_source is not None
+ assert intro_source is not None
+ assert title is not None
+
+ rows, columns_order = parse_table(lines=table_source)
+
+ table = ""
+
+ for i, row in enumerate(rows):
+ if i == 0:
+ line = ["" + ' '.join(row[k]) + " | " for k in columns_order]
+ else:
+ line = []
+ for column in columns_order:
+ val = row.get(column, [])
+ line.append('' + ' '.join(val) + '
| ')
+
+ table += "\n" + '\n'.join(line) + "\n
\n"
+
+
+ final = HTML_TEMPLATE.substitute({
+ "title": title,
+ "css": "\n".join(css_source),
+ "table": table,
+ "intro": "\n".join(intro_source)
+ })
+
+ with open(target_path, "w") as f:
+ f.write(final)
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Build language comparison chart")
+ parser.add_argument(nargs='+', metavar="TEMPLATE", dest="templates", help="Template file that will be converted to HTML page")
+
+ args = parser.parse_args()
+
+ for template in args.templates:
+ dst, _ = os.path.splitext(template)
+ dst += ".html"
+ compile_template(template_path=template, target_path=dst)
+
+if __name__ == "__main__":
+ main()