about summary refs log tree commit diff stats
path: root/tutorial
diff options
context:
space:
mode:
Diffstat (limited to 'tutorial')
-rw-r--r--tutorial/add2.mu19
-rw-r--r--tutorial/c2f.mu19
-rw-r--r--tutorial/converter.mu138
-rw-r--r--tutorial/converter2.mu161
-rw-r--r--tutorial/counter.mu35
-rw-r--r--tutorial/index.md169
-rw-r--r--tutorial/task1.pngbin0 -> 10546 bytes
-rw-r--r--tutorial/task2.mu7
-rw-r--r--tutorial/task2.pngbin0 -> 10513 bytes
-rw-r--r--tutorial/task3.pngbin0 -> 94644 bytes
-rw-r--r--tutorial/task4-calculator.html15
-rw-r--r--tutorial/task4-initial.pngbin0 -> 10950 bytes
-rw-r--r--tutorial/task4-solution.mu15
-rw-r--r--tutorial/task4.mu15
-rw-r--r--tutorial/vimrc.vim2
15 files changed, 595 insertions, 0 deletions
diff --git a/tutorial/add2.mu b/tutorial/add2.mu
new file mode 100644
index 00000000..250d72ca
--- /dev/null
+++ b/tutorial/add2.mu
@@ -0,0 +1,19 @@
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  var result/eax: int <- do-add 3, 4
+  draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, result, 3/fg=cyan 0/bg
+}
+
+fn do-add a: int, b: int -> _/eax: int {
+  var result/eax: int <- copy a
+  result <- add b
+  return result
+}
+
+fn test-do-add {
+  var observed/eax: int <- do-add 0, 0
+  check-ints-equal observed, 0, "F - 0+0"
+  observed <- do-add 3, 0
+  check-ints-equal observed, 3, "F - 3+0"
+  observed <- do-add 3, 2
+  check-ints-equal observed, 5, "F - 3+2"
+}
diff --git a/tutorial/c2f.mu b/tutorial/c2f.mu
new file mode 100644
index 00000000..250d72ca
--- /dev/null
+++ b/tutorial/c2f.mu
@@ -0,0 +1,19 @@
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  var result/eax: int <- do-add 3, 4
+  draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, result, 3/fg=cyan 0/bg
+}
+
+fn do-add a: int, b: int -> _/eax: int {
+  var result/eax: int <- copy a
+  result <- add b
+  return result
+}
+
+fn test-do-add {
+  var observed/eax: int <- do-add 0, 0
+  check-ints-equal observed, 0, "F - 0+0"
+  observed <- do-add 3, 0
+  check-ints-equal observed, 3, "F - 3+0"
+  observed <- do-add 3, 2
+  check-ints-equal observed, 5, "F - 3+2"
+}
diff --git a/tutorial/converter.mu b/tutorial/converter.mu
new file mode 100644
index 00000000..a8aa26e3
--- /dev/null
+++ b/tutorial/converter.mu
@@ -0,0 +1,138 @@
+# Temperature Converter app
+#   https://eugenkiss.github.io/7guis/tasks/#temp
+#
+# To build:
+#   $ ./translate converter.mu
+# To run:
+#   $ qemu-system-i386 code.img
+
+# todo:
+#   less duplication
+#   error checking for input without hard-aborting
+
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  # celsius numeric representation
+  var zero: float
+  var celsius/xmm1: float <- fahrenheit-to-celsius zero
+  # celsius string representation
+  var s-storage: (stream byte 0x10)
+  var s/ecx: (addr stream byte) <- address s-storage
+  write-float-decimal-approximate s, celsius, 2/decimal-places
+  # celsius input/display
+  var celsius-input-storage: gap-buffer
+  var celsius-input/esi: (addr gap-buffer) <- address celsius-input-storage
+  initialize-gap-buffer celsius-input, 8/capacity
+  load-gap-buffer-from-stream celsius-input, s
+  var cursor-in-celsius?/edx: boolean <- copy 0xffffffff/true
+  # fahrenheit numeric representation
+  var fahrenheit/xmm2: float <- celsius-to-fahrenheit celsius
+  # fahrenheit string representation
+  clear-stream s
+  write-float-decimal-approximate s, fahrenheit, 2/decimal-places
+  # fahrenheit input/display
+  var fahrenheit-input-storage: gap-buffer
+  var fahrenheit-input/edi: (addr gap-buffer) <- address fahrenheit-input-storage
+  initialize-gap-buffer fahrenheit-input, 8/capacity
+  load-gap-buffer-from-stream fahrenheit-input, s
+  var cursor-in-fahrenheit?/ebx: boolean <- copy 0/false  # exactly one cursor boolean must be true at any time
+  # widget title
+  set-cursor-position screen, 0x1f/x 0xe/y
+  draw-text-rightward-from-cursor-over-full-screen screen, " Converter                            ", 0xf/fg 0x16/bg
+  # event loop
+  {
+    # draw current state to screen
+    clear-rect screen, 0x1f/xmin 0xf/ymin, 0x45/xmax 0x14/ymax, 0xc5/color
+    var x/eax: int <- render-gap-buffer screen, celsius-input, 0x20/x 0x10/y, cursor-in-celsius?, 7/fg 0/bg
+    x <- draw-text-rightward screen, " celsius = ", x, 0x45/xmax, 0x10/y, 7/fg 0xc5/bg
+    x <- render-gap-buffer screen, fahrenheit-input, x 0x10/y, cursor-in-fahrenheit?, 7/fg 0/bg
+    x <- draw-text-rightward screen, " fahrenheit", x, 0x45/xmax, 0x10/y, 7/fg 0xc5/bg
+    # render a menu bar
+    set-cursor-position screen, 0x21/x 0x12/y
+    draw-text-rightward-from-cursor-over-full-screen screen, " tab ", 0/fg 0x5c/bg=highlight
+    draw-text-rightward-from-cursor-over-full-screen screen, " switch sides ", 7/fg 0xc5/bg
+    draw-text-rightward-from-cursor-over-full-screen screen, " enter ", 0/fg 0x5c/bg=highlight
+    draw-text-rightward-from-cursor-over-full-screen screen, " convert ", 7/fg 0xc5/bg
+    # process a single keystroke
+    $main:input: {
+      var key/eax: byte <- read-key keyboard
+      var key/eax: grapheme <- copy key
+      compare key, 0
+      loop-if-=
+      # tab = switch cursor between input areas
+      compare key, 9/tab
+      {
+        break-if-!=
+        cursor-in-celsius? <- not
+        cursor-in-fahrenheit? <- not
+        break $main:input
+      }
+      # enter = convert in appropriate direction
+      compare key, 0xa/newline
+      {
+        break-if-!=
+        {
+          compare cursor-in-celsius?, 0/false
+          break-if-=
+          clear-stream s
+          emit-gap-buffer celsius-input, s
+          celsius <- parse-float-decimal s
+          fahrenheit <- celsius-to-fahrenheit celsius
+          clear-stream s
+          write-float-decimal-approximate s, fahrenheit, 2/decimal-places
+          clear-gap-buffer fahrenheit-input
+          load-gap-buffer-from-stream fahrenheit-input, s
+        }
+        {
+          compare cursor-in-fahrenheit?, 0/false
+          break-if-=
+          clear-stream s
+          emit-gap-buffer fahrenheit-input, s
+          {
+            var tmp/xmm1: float <- parse-float-decimal s
+            fahrenheit <- copy tmp
+          }
+          celsius <- fahrenheit-to-celsius fahrenheit
+          clear-stream s
+          write-float-decimal-approximate s, celsius, 2/decimal-places
+          clear-gap-buffer celsius-input
+          load-gap-buffer-from-stream celsius-input, s
+        }
+        break $main:input
+      }
+      # otherwise pass key to appropriate input area
+      compare cursor-in-celsius?, 0/false
+      {
+        break-if-=
+        edit-gap-buffer celsius-input, key
+        break $main:input
+      }
+      compare cursor-in-fahrenheit?, 0/false
+      {
+        break-if-=
+        edit-gap-buffer fahrenheit-input, key
+        break $main:input
+      }
+    }
+    loop
+  }
+}
+
+fn fahrenheit-to-celsius f: float -> _/xmm1: float {
+  var result/xmm1: float <- copy f
+  var thirty-two/eax: int <- copy 0x20
+  var thirty-two-f/xmm0: float <- convert thirty-two
+  result <- subtract thirty-two-f
+  var factor/xmm0: float <- rational 5, 9
+  result <- multiply factor
+  return result
+}
+
+fn celsius-to-fahrenheit c: float -> _/xmm2: float {
+  var result/xmm1: float <- copy c
+  var factor/xmm0: float <- rational 9, 5
+  result <- multiply factor
+  var thirty-two/eax: int <- copy 0x20
+  var thirty-two-f/xmm0: float <- convert thirty-two
+  result <- add thirty-two-f
+  return result
+}
diff --git a/tutorial/converter2.mu b/tutorial/converter2.mu
new file mode 100644
index 00000000..c5dde9f5
--- /dev/null
+++ b/tutorial/converter2.mu
@@ -0,0 +1,161 @@
+# Temperature Converter app
+#   https://eugenkiss.github.io/7guis/tasks/#temp
+#
+# To build:
+#   $ ./translate converter2.mu
+# To run:
+#   $ qemu-system-i386 code.img
+
+# todo:
+#   error checking for input without hard-aborting
+
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  # imgui approach
+  forever {
+    number-input fahrenheit, cursor-in-fahrenheit?
+    number-input celsius, cursor-in-celsius?
+    if (menu-key 9/tab "Tab" "switch sides") {  # requires non-blocking input
+      cursor-in-celsius? <- not
+      cursor-in-fahrenheit? <- not
+    }
+    if (menu-key 0xa/newline "Enter" "convert") {
+      if cursor-in-fahrenheit
+        celsius = fahrenheit-to-celsius fahrenheit
+      else
+        fahrenheit = celsius-to-fahrenheit celsius
+    }
+  }
+  # celsius numeric representation
+  var zero: float
+  var celsius/xmm1: float <- fahrenheit-to-celsius zero
+  # celsius input/display
+  var celsius-input-storage: gap-buffer
+  var celsius-input/esi: (addr gap-buffer) <- address celsius-input-storage
+  initialize-gap-buffer celsius-input, 8/capacity
+  value-to-input celsius, celsius-input
+  # fahrenheit numeric representation
+  var fahrenheit/xmm2: float <- celsius-to-fahrenheit celsius
+  # fahrenheit input/display
+  var fahrenheit-input-storage: gap-buffer
+  var fahrenheit-input/edi: (addr gap-buffer) <- address fahrenheit-input-storage
+  initialize-gap-buffer fahrenheit-input, 8/capacity
+  value-to-input fahrenheit, fahrenheit-input
+  # cursor toggle
+  var cursor-in-celsius?/edx: boolean <- copy 0xffffffff/true
+  #
+  render-title screen
+  # event loop
+  {
+    # render
+    render-state celsius-input, fahrenheit-input, cursor-in-celsius?
+    render-menu-bar screen
+    # process a single keystroke
+    $main:input: {
+      var key/eax: byte <- read-key keyboard
+      var key/eax: grapheme <- copy key
+      compare key, 0
+      loop-if-=
+      # tab = switch cursor between input areas
+      compare key, 9/tab
+      {
+        break-if-!=
+        cursor-in-celsius? <- not
+        break $main:input
+      }
+      # enter = convert in appropriate direction
+      compare key, 0xa/newline
+      {
+        break-if-!=
+        {
+          compare cursor-in-celsius?, 0/false
+          break-if-=
+          var tmp/xmm0: float <- input-to-value celsius-input
+          celsius <- copy tmp
+          fahrenheit <- celsius-to-fahrenheit celsius
+          value-to-input fahrenheit, fahrenheit-input
+          break $main:input
+        }
+        var tmp/xmm0: float <- input-to-value fahrenheit-input
+        fahrenheit <- copy tmp
+        celsius <- fahrenheit-to-celsius fahrenheit
+        value-to-input celsius, celsius-input
+        break $main:input
+      }
+      # otherwise pass key to appropriate input area
+      compare cursor-in-celsius?, 0/false
+      {
+        break-if-=
+        edit-gap-buffer celsius-input, key
+        break $main:input
+      }
+      edit-gap-buffer fahrenheit-input, key
+    }
+    loop
+  }
+}
+
+# business logic
+
+fn fahrenheit-to-celsius f: float -> _/xmm1: float {
+  var result/xmm1: float <- copy f
+  var thirty-two/eax: int <- copy 0x20
+  var thirty-two-f/xmm0: float <- convert thirty-two
+  result <- subtract thirty-two-f
+  var factor/xmm0: float <- rational 5, 9
+  result <- multiply factor
+  return result
+}
+
+fn celsius-to-fahrenheit c: float -> _/xmm2: float {
+  var result/xmm1: float <- copy c
+  var factor/xmm0: float <- rational 9, 5
+  result <- multiply factor
+  var thirty-two/eax: int <- copy 0x20
+  var thirty-two-f/xmm0: float <- convert thirty-two
+  result <- add thirty-two-f
+  return result
+}
+
+# helpers for UI
+
+fn input-to-value in: (addr gap-buffer) -> _/xmm0: float {
+  var s-storage: (stream byte 0x10)
+  var s/ecx: (addr stream byte) <- address s-storage
+  emit-gap-buffer in, s
+  var result/xmm1: float <- parse-float-decimal s
+  return result
+}
+
+fn value-to-input in: float, out: (addr gap-buffer) {
+  var s-storage: (stream byte 0x10)
+  var s/ecx: (addr stream byte) <- address s-storage
+  write-float-decimal-approximate s, in, 2/decimal-places
+  clear-gap-buffer out
+  load-gap-buffer-from-stream out, s
+}
+
+# helpers for rendering to screen
+# magic constants here need to be consistent between functions
+
+fn render-title screen: (addr screen) {
+  set-cursor-position screen, 0x1f/x 0xe/y
+  draw-text-rightward-from-cursor-over-full-screen screen, " Converter                            ", 0xf/fg 0x16/bg
+}
+
+fn render-state screen: (addr screen), c: (addr gap-buffer), f: (addr gap-buffer), cursor-in-c?: boolean {
+  clear-rect screen, 0x1f/xmin 0xf/ymin, 0x45/xmax 0x14/ymax, 0xc5/color
+  var x/eax: int <- render-gap-buffer screen, c, 0x20/x 0x10/y, cursor-in-c?, 7/fg 0/bg
+  x <- draw-text-rightward screen, " celsius = ", x, 0x45/xmax, 0x10/y, 7/fg 0xc5/bg
+  var cursor-in-f?/ecx: boolean <- copy cursor-in-c?
+  cursor-in-f? <- not
+  x <- render-gap-buffer screen, f, x 0x10/y, cursor-in-f?, 7/fg 0/bg
+  x <- draw-text-rightward screen, " fahrenheit", x, 0x45/xmax, 0x10/y, 7/fg 0xc5/bg
+}
+
+fn render-menu-bar screen: (addr screen) {
+  set-cursor-position screen, 0x21/x 0x12/y
+  draw-text-rightward-from-cursor-over-full-screen screen, " tab ", 0/fg 0x5c/bg=highlight
+  draw-text-rightward-from-cursor-over-full-screen screen, " switch sides ", 7/fg 0xc5/bg
+  draw-text-rightward-from-cursor-over-full-screen screen, " enter ", 0/fg 0x5c/bg=highlight
+  draw-text-rightward-from-cursor-over-full-screen screen, " convert ", 7/fg 0xc5/bg
+}
diff --git a/tutorial/counter.mu b/tutorial/counter.mu
new file mode 100644
index 00000000..b1f3cc42
--- /dev/null
+++ b/tutorial/counter.mu
@@ -0,0 +1,35 @@
+# Counter app
+#   https://eugenkiss.github.io/7guis/tasks/#counter
+#
+# To build:
+#   $ ./translate counter.mu
+# To run:
+#   $ qemu-system-i386 code.img
+
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  var count/ecx: int <- copy 0
+  # widget title
+  set-cursor-position screen, 0x1f/x 0xe/y
+  draw-text-rightward-from-cursor-over-full-screen screen, " Counter                         ", 0xf/fg 0x16/bg
+  # event loop
+  {
+    # draw current state to screen
+    clear-rect screen, 0x1f/xmin 0xf/ymin, 0x40/xmax 0x14/ymax, 0xc5/color
+    set-cursor-position screen, 0x20/x 0x10/y
+    draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, count, 7/fg 0xc5/bg
+    # render a menu bar
+    set-cursor-position screen, 0x24/x 0x12/y
+    draw-text-rightward-from-cursor-over-full-screen screen, " enter ", 0/fg 0x5c/bg=highlight
+    draw-text-rightward-from-cursor-over-full-screen screen, " increment ", 7/fg 0xc5/bg
+    # process a single keystroke
+    {
+      var key/eax: byte <- read-key keyboard
+      compare key, 0
+      loop-if-=
+      compare key, 0xa/newline
+      break-if-!=
+      count <- increment
+    }
+    loop
+  }
+}
diff --git a/tutorial/index.md b/tutorial/index.md
new file mode 100644
index 00000000..9cbc5e9f
--- /dev/null
+++ b/tutorial/index.md
@@ -0,0 +1,169 @@
+# A slow tour through Mu software on x86 computers
+
+[Mu](https://github.com/akkartik/mu) shrinks all the software in a computer
+until it can (in principle) fit in a single head. Sensible error messages with
+as little code as possible, starting all the way from your (x86) processor's
+instruction set. Everything easy to change to your needs
+([habitable](http://akkartik.name/post/habitability)), everything easy to
+check up on ([auditable](http://akkartik.name/post/neighborhood)).
+
+This page is a guided tour through Mu's Readme and reference documentation.
+We'll start out really slow and gradually accelerate as we build up skills. By
+the end of it all, I hope you'll be able to program your processor to run some
+small graphical programs. The programs will only use a small subset of your
+computer's capabilities; there's still a lot I don't know and therefore cannot
+teach. However, the programs will run on a _real_ processor without needing
+any other intermediary software.
+
+_Prerequisites_
+
+You will need:
+
+* A computer with an x86 processor running Linux. We're going to slowly escape
+  Linux, but we'll need it at the start. Mu works on other platforms, but be
+  warned that things will be _much_ (~20x) slower.
+* Some fluency in typing commands at the terminal and interpreting their
+  output.
+* Fluency with some text editor. Things like undo, copying and pasting text,
+  and saving work in files. A little experience programming in _some_ language
+  is also handy.
+* [Git](https://git-scm.com) for version control.
+* [QEMU](https://www.qemu.org) for emulating a processor without Linux.
+* Basic knowledge of number bases, and the difference between decimal and
+  hexadecimal numbers.
+
+If you have trouble with any of this, [I'm always nearby and available to
+answer questions](http://akkartik.name/contact). The prerequisites are just
+things I haven't figured out how to explain yet. In particular, I want this
+page to be accessible to people who are in the process of learning
+programming, but I'm sure it isn't good enough yet for that. Ask me questions
+and help me improve it.
+
+# Task 1: getting started
+
+Open a terminal and run the following commands to prepare Mu on your computer:
+
+```
+git clone https://github.com/akkartik/mu
+cd mu
+```
+
+Run a small program to start:
+
+```
+./translate apps/ex5.mu
+qemu-system-i386 code.img
+```
+
+If you aren't on Linux, the command for creating `code.img` will be slightly
+different:
+
+```
+./translate_emulated apps/ex5.mu
+qemu-system-i386 code.img
+```
+
+Either way, you should see this:
+
+<img alt='screenshot of hello world on the Mu computer' src='task1.png'>
+
+If you have any trouble at this point, don't waste _any_ time thinking about
+it. Just [get in touch](http://akkartik.name/contact).
+
+(You can look at `apps/ex5.mu` at this point if you like. It's just a few
+lines long. But don't worry if it doesn't make much sense.)
+
+# Task 2: running tests
+
+Here's a new program to run:
+
+```
+./translate tutorial/task2.mu
+qemu-system-i386 code.img
+```
+
+(As before, I'll leave you to substitute `translate` with `translate_emulated`
+if you're not on Linux.)
+
+This time the screen will look like this:
+
+<img alt='screenshot of failing test on the Mu computer' src='task2.png'>
+
+Each of the dots is a _test_, a little self-contained and automated program
+run with an expected result. Mu comes with a lot of tests, and it always runs
+all tests before it runs any program. You may have missed the dots when you
+ran Task 1 because there were no failures. They were printed on the screen and
+then immediately erased. In Task 2, however, we've deliberately included a
+failing test. When any tests fail, Mu will immediately stop, showing you
+messages from failing tests and implicitly asking you to first fix them.
+
+(Don't worry just yet about what the message in the middle of all the dots means.)
+
+# Task 3: configure your text editor
+
+So far we haven't used a text editor yet, but we will now be starting to do
+so. Before we do, it's worth spending a little bit of time setting your
+preferred editor up to be a little more ergonomic. Mu comes with _syntax
+highlighting_ settings for a few common text editors in the `editor/`
+sub-directory. If you don't see your text editor there, or if you don't know
+what to do with those files, [get in touch!](http://akkartik.name/contact)
+Here's what my editor (Vim) looks like with these settings on the program of
+Task 1:
+
+<img alt='Vim text editor rendering some colors in a Mu program' src='task3.png'>
+
+It's particularly useful to highlight _comments_ which the computer ignores
+(everything on a line after a `#` character) and _strings_ within `""` double
+quotes.
+
+# Task 4: your first Mu statement
+
+Mu is a statement-oriented language. Read the first section of the [Mu syntax
+description](https://github.com/akkartik/mu/blob/main/mu.md) to learn a little
+bit about it.
+
+Here's a skeleton of a Mu function that's missing a single statement.
+
+```
+fn the-answer -> _/eax: int {
+  var result/eax: int <- copy 0
+  # insert your statement below {
+
+  # }
+  return result
+}
+```
+
+Try running it now:
+```
+./translate tutorial/task4.mu
+qemu-system-i386 code.img
+```
+
+(As before, I'll leave you to substitute `translate` with `translate_emulated`
+if you're not on Linux.)
+
+You should see a failing test that looks something like this:
+
+<img alt='screenshot of the initial (failing) state of task 4' src='task4-initial.png'>
+
+Open `tutorial/task4.mu` in your text editor. Think about how to add a line
+between the `{}` lines to make `the-answer` return 42. Rerun the above
+commands. You'll know you got it right all the tests pass, i.e. when the rows
+of dots and text above are replaced by an empty screen.
+
+Don't be afraid to run the above commands over and over again as you try out
+different solutions. Here's a way to run them together so they're easy to
+repeat.
+
+```
+./translate tutorial/task4.mu  &&  qemu-system-i386 code.img
+```
+
+In programming there is no penalty for making mistakes, and once you arrive at
+the correct solution you have it forever. As always, [feel free to ping me and
+ask questions or share your experience](http://akkartik.name/contact).
+
+One gotcha to keep in mind is that numbers in Mu must always be in hexadecimal
+notation, starting with `0x`. Use a calculator on your computer or phone to
+convert 42 to hexadecimal, or [this page on your web browser](http://akkartik.github.io/mu/tutorial/task4-calculator.html).
diff --git a/tutorial/task1.png b/tutorial/task1.png
new file mode 100644
index 00000000..0009990f
--- /dev/null
+++ b/tutorial/task1.png
Binary files differdiff --git a/tutorial/task2.mu b/tutorial/task2.mu
new file mode 100644
index 00000000..c31efc0f
--- /dev/null
+++ b/tutorial/task2.mu
@@ -0,0 +1,7 @@
+fn test-1 {
+  check-ints-equal 1, 2, "F - test-1"
+}
+
+fn main {
+  # do nothing
+}
diff --git a/tutorial/task2.png b/tutorial/task2.png
new file mode 100644
index 00000000..6b87e727
--- /dev/null
+++ b/tutorial/task2.png
Binary files differdiff --git a/tutorial/task3.png b/tutorial/task3.png
new file mode 100644
index 00000000..82b5f594
--- /dev/null
+++ b/tutorial/task3.png
Binary files differdiff --git a/tutorial/task4-calculator.html b/tutorial/task4-calculator.html
new file mode 100644
index 00000000..dca1190c
--- /dev/null
+++ b/tutorial/task4-calculator.html
@@ -0,0 +1,15 @@
+<script>
+function convert() {
+  var n = parseInt(document.getElementById('input').value);
+  if (isNaN(n))
+    document.getElementById('result').innerHTML = "not a number";
+  else
+    document.getElementById('result').innerHTML = "0x" + n.toString(16);
+  event.preventDefault();
+}
+</script>
+<body style='font-size:200%'>
+<form style='margin:5em' onSubmit='convert()'>
+<input style='width:4em; font-size:100%' type='text' id='input'/> <input type='submit' style='width=6em; font-size:100%' value='convert'> = <span id='result' style='display:inline-block; min-width:4em; border-bottom:1px solid; text-align: right'></span>
+</form>
+</body>
diff --git a/tutorial/task4-initial.png b/tutorial/task4-initial.png
new file mode 100644
index 00000000..99b5514a
--- /dev/null
+++ b/tutorial/task4-initial.png
Binary files differdiff --git a/tutorial/task4-solution.mu b/tutorial/task4-solution.mu
new file mode 100644
index 00000000..060b7123
--- /dev/null
+++ b/tutorial/task4-solution.mu
@@ -0,0 +1,15 @@
+fn the-answer -> _/eax: int {
+  var result/eax: int <- copy 0
+  # insert your statement below {
+  result <- copy 0x2a
+  # }
+  return result
+}
+
+fn test-the-answer {
+  var result/eax: int <- the-answer
+  check-ints-equal result, 0x2a, "F - the-answer should return 42, but didn't."
+}
+
+fn main {
+}
diff --git a/tutorial/task4.mu b/tutorial/task4.mu
new file mode 100644
index 00000000..ca8da8f4
--- /dev/null
+++ b/tutorial/task4.mu
@@ -0,0 +1,15 @@
+fn the-answer -> _/eax: int {
+  var result/eax: int <- copy 0
+  # insert your statement below {
+
+  # }
+  return result
+}
+
+fn test-the-answer {
+  var result/eax: int <- the-answer
+  check-ints-equal result, 0x2a, "F - the-answer should return 42, but didn't."
+}
+
+fn main {
+}
diff --git a/tutorial/vimrc.vim b/tutorial/vimrc.vim
new file mode 100644
index 00000000..348fe364
--- /dev/null
+++ b/tutorial/vimrc.vim
@@ -0,0 +1,2 @@
+" when opening files in this directory, load vimrc from cwd (top-level)
+source vimrc.vim