Kicking the Tires

It is somewhat hard to believe, but with a few simple extensions we’ve covered in the last chapters, we have grown a real-ish language. With this, we can do a lot of interesting things, including I/O, math, and a bunch of other things. For example, we can now add a nice sequencing operator (printd is defined to print out the specified value and a newline):

  1. ready> extern printd(x);
  2. declare double @printd(double)
  3. ready> def binary : 1 (x y) 0;
  4. ..
  5. ready> printd(123) : printd(456) : printd(789);
  6. 123.000000
  7. 456.000000
  8. 789.000000
  9. Evaluated to 0.000000

We can also define a bunch of other “primitive” operations, such as:

  1. # Logical unary not.
  2. def unary!(v)
  3. if v then
  4. 0
  5. else
  6. 1;
  7. # Unary negate.
  8. def unary-(v)
  9. 0-v;
  10. # Define > with the same precedence as <.
  11. def binary> 10 (LHS RHS)
  12. RHS < LHS;
  13. # Binary logical or, which does not short circuit.
  14. def binary| 5 (LHS RHS)
  15. if LHS then
  16. 1
  17. else if RHS then
  18. 1
  19. else
  20. 0;
  21. # Binary logical and, which does not short circuit.
  22. def binary& 6 (LHS RHS)
  23. if !LHS then
  24. 0
  25. else
  26. !!RHS;
  27. # Define = with slightly lower precedence than relationals.
  28. def binary = 9 (LHS RHS)
  29. !(LHS < RHS | LHS > RHS);
  30. # Define ':' for sequencing: as a low-precedence operator that ignores operands
  31. # and just returns the RHS.
  32. def binary : 1 (x y) y;

Given the previous if/then/else support, we can also define interesting functions for I/O. For example, the following prints out a character whose “density” reflects the value passed in: the lower the value, the denser the character:

  1. ready>
  2. extern putchard(char);
  3. def printdensity(d)
  4. if d > 8 then
  5. putchard(32) # ' '
  6. else if d > 4 then
  7. putchard(46) # '.'
  8. else if d > 2 then
  9. putchard(43) # '+'
  10. else
  11. putchard(42); # '*'
  12. ...
  13. ready> printdensity(1): printdensity(2): printdensity(3):
  14. printdensity(4): printdensity(5): printdensity(9):
  15. putchard(10);
  16. **++.
  17. Evaluated to 0.000000

The Mandelbrot set is a set of two dimensional points generated by the complex function z = z2 + c whose boundary forms a fractal.

Kicking the Tires - 图1

Based on our simple primitive operations defined above, we can start to define more interesting things. For example, here’s a little function that solves for the number of iterations it takes a function in the complex plane to converge:

  1. # Determine whether the specific location diverges.
  2. # Solve for z = z^2 + c in the complex plane.
  3. def mandelconverger(real imag iters creal cimag)
  4. if iters > 255 | (real*real + imag*imag > 4) then
  5. iters
  6. else
  7. mandelconverger(real*real - imag*imag + creal,
  8. 2*real*imag + cimag,
  9. iters+1, creal, cimag);
  10. # Return the number of iterations required for the iteration to escape
  11. def mandelconverge(real imag)
  12. mandelconverger(real, imag, 0, real, imag);

Our mandelconverge function returns the number of iterations that it takes for a complex orbit to escape, saturating to 255. This is not a very useful function by itself, but if we plot its value over a two-dimensional plane, we can see the Mandelbrot set. Given that we are limited to using putchard here, our amazing graphical output is limited, but we can whip together something using the density plotter above:

  1. # Compute and plot the mandelbrot set with the specified 2 dimensional range
  2. # info.
  3. def mandelhelp(xmin xmax xstep ymin ymax ystep)
  4. for y = ymin, y < ymax, ystep in (
  5. (for x = xmin, x < xmax, xstep in
  6. printdensity(mandelconverge(x,y)))
  7. : putchard(10)
  8. );
  9. # mandel - This is a convenient helper function for plotting the mandelbrot set
  10. # from the specified position with the specified Magnification.
  11. def mandel(realstart imagstart realmag imagmag)
  12. mandelhelp(realstart, realstart+realmag*78, realmag,
  13. imagstart, imagstart+imagmag*40, imagmag);

Given this, we can try plotting out the mandelbrot set! Let’s try it out:

  1. ******************************************************************************
  2. ******************************************************************************
  3. ****************************************++++++********************************
  4. ************************************+++++...++++++****************************
  5. *********************************++++++++.. ...+++++**************************
  6. *******************************++++++++++.. ..+++++*************************
  7. ******************************++++++++++. ..++++++************************
  8. ****************************+++++++++.... ..++++++***********************
  9. **************************++++++++....... .....++++**********************
  10. *************************++++++++. . ... .++*********************
  11. ***********************++++++++... ++*********************
  12. *********************+++++++++.... .+++********************
  13. ******************+++..+++++.... ..+++*******************
  14. **************++++++. .......... +++*******************
  15. ***********++++++++.. .. .++*******************
  16. *********++++++++++... .++++******************
  17. ********++++++++++.. .++++******************
  18. *******++++++..... ..++++******************
  19. *******+........ ...++++******************
  20. *******+... .... ...++++******************
  21. *******+++++...... ..++++******************
  22. *******++++++++++... .++++******************
  23. *********++++++++++... ++++******************
  24. **********+++++++++.. .. ..++*******************
  25. *************++++++.. .......... +++*******************
  26. ******************+++...+++..... ..+++*******************
  27. *********************+++++++++.... ..++********************
  28. ***********************++++++++... +++********************
  29. *************************+++++++.. . ... .++*********************
  30. **************************++++++++....... ......+++**********************
  31. ****************************+++++++++.... ..++++++***********************
  32. *****************************++++++++++.. ..++++++************************
  33. *******************************++++++++++.. ...+++++*************************
  34. *********************************++++++++.. ...+++++**************************
  35. ***********************************++++++....+++++****************************
  36. ***************************************++++++++*******************************
  37. ******************************************************************************
  38. ******************************************************************************
  39. ******************************************************************************
  40. ******************************************************************************

At this point, you may be starting to realize that Kaleidoscope is a real and powerful language. It may not be self-similar :), but it can be used to plot things that are!

With this, we conclude the “adding user-defined operators” chapter of the tutorial. We have successfully augmented our language, adding the ability to extend the language in the library, and we have shown how this can be used to build a simple but interesting end-user application in Kaleidoscope. At this point, Kaleidoscope can build a variety of applications that are functional and can call functions with side-effects, but it can’t actually define and mutate a variable itself.

Strikingly, variable mutation is an important feature of imperative languages, and it is not at all obvious how to add support for mutable variables without having to add an “SSA construction” phase to our front-end. In the next chapter, we will describe how we can add variable mutation without building SSA in our front-end.