DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 97: Quackery

Quackery is a simple stack-based languge embedded in Python. It describes itself as "lightweight language for recreational and educational programming, inspired by Forth and Lisp", so let's see how fun it is, without considering its production use.

Hello, World!

Installation is quite messy, I needed to checkout the git repo, add #!/usr/bin/env python3 on top of quackery.py, chmod +x and symlink it. It really doesn't take that much work to provive a pip package, so hopefully the author does it. Otherwise it's difficult to recommend it even for casual play.

say "Hello, World!"
cr
Enter fullscreen mode Exit fullscreen mode

It works just fine, but annoyingly always displays two extra newlines at the end, to show its empty stack.

$ quackery hello.qky
Hello, World!


Enter fullscreen mode Exit fullscreen mode

cr prints a newline. It used to be called "carriage return" in the olden days and some old languages had it as cr, it feels silly nowadays, nl would be a far better name.

say is a bit more interesting. As Quackery is generally stack-based you'd think it should be something like "Hello, World!" say. That actually doesn't work - Quackery doesn't have strings. say is a special kind of "word" (a "builder"), that does some special things while parsing, intstead of just normally working with what's on the stack.

Working with Strings

Quackery has three types. Numbers, lists ("nests"), and functions. Notably no booleans (it's just 1 and 0), and no strings (just "nests" of numbers).

We can just do a stack-based Hello, World!

72 emit
101 emit
108 emit
108 emit
111 emit
44 emit
32 emit
87 emit
111 emit
114 emit
108 emit
100 emit
33 emit
13 emit
Enter fullscreen mode Exit fullscreen mode

Annoyingly Quackery does not actually print what we send, it does some weird filtering, and it insists that 13 (CR) is the newline character, even though it's actually 10 (NL) on every system. I have no idea why, it's baffling. The last system that used CR-based line ending remembers the Cold War.

We can put the string in a nested list (and quote it to make it clear we don't want to execute it), then use witheach emit to iterate it:

' [ 72 101 108 108 111 44 32 87 111 114 108 100 33 13 ] witheach emit
Enter fullscreen mode Exit fullscreen mode

There are macros for both these $ followed by a string puts that nested list of byte values on top of the stack, and echo$ does the witheach emit loop. There's no escape codes like \r or \n, so we need to do cr separately:

$ "Hello, World!" echo$
cr
Enter fullscreen mode Exit fullscreen mode

This was all surprisingly painful for a Hello, World!

Unicode

Quackery has the absolutely worst Unicode support of any language so far. All others have at least decency of passing through any characters they don't care about:

say "Żółw eats 🍨"
cr
Enter fullscreen mode Exit fullscreen mode
$ quackery unicode.qky
???w eats ?
Enter fullscreen mode Exit fullscreen mode

We can what goes on the stack with this program:

$ "Żółw eats 🍨"
Enter fullscreen mode Exit fullscreen mode
$ quackery unicode2.qky
[ 379 243 322 119 32 101 97 116 115 32 127848 ]
Enter fullscreen mode Exit fullscreen mode

So the codepoints get pushed onto the nested stack just fine, but then emit replaces them with ? for some crazy reason.

This is baffling af. This isn't some language from the 1980s, it was created in December 2020, and keeps getting regular updates. Why is it actively trying to fight Unicode, while just not giving af and sending whatever number we have out would have worked! Here's what Quackery does if I remove that stupid ASCII check in emit:

$ quackery unicode.qky
Żółw eats 🍨
Enter fullscreen mode Exit fullscreen mode

It's not much, but at least them we can build our own Unicode support.

Math

So far we encountered more exceptions than cases where it actually did, but Quackery is mostly a stack-based language:

20 18 3 + * echo cr
Enter fullscreen mode Exit fullscreen mode
$ quackery math.qky
420
Enter fullscreen mode Exit fullscreen mode

Each number pushes itself to the stack, and each mathematical operator pops two numbers off the stack, and pushes the result back. echo pops the top number from the stack and prints it as a decimal number (unlike emit which prints it as ASCII code).

Then we need to add cr for extra newline.

Hello, Name

[
  $ "Hello, " echo$  ( print "Hello, " )
  echo$              ( print the name )
  $ "!" echo$ cr     ( print "!\n" )
] is hello

$ "What is your name? " input
hello
Enter fullscreen mode Exit fullscreen mode
$ quackery name.qky
What is your name? Bella
Hello, Bella!
Enter fullscreen mode Exit fullscreen mode

We cat define functions with [ ... ] is <name>. Functions take some arguments from stack and return any number of values to the stack.

Comments use ( ... ) - note the spaces, everything in Quackery (as well as most other stack-based languages need spaces as separators).

input takes prompt (in form of list of numbers) as argument and returns user input (again, in form of list of numbers) to the stack.

Loop

There are a few ways to loop. The most obvious is <n> times [ ... ], but it's weird. It doesn't push counter on the stack as one would expect, you need to call i to get the counter. And it does count down (9 to 0), not count up (0 to 9).

10 times [ i echo cr ]
Enter fullscreen mode Exit fullscreen mode
$ quackery loop.qky
9
8
7
6
5
4
3
2
1
0
Enter fullscreen mode Exit fullscreen mode

If we want nice loop, we'd need to translate that. 10-i is the number we want:

10 times [ 10 i - echo cr ]

$ quackery loop2.qky
1
2
3
4
5
6
7
8
9
10
Enter fullscreen mode Exit fullscreen mode

Fizzbuzz

[
  dup 15 mod 0 =
  iff [
    drop say "FizzBuzz"
  ] else [
    dup 5 mod 0 =
    iff [
      drop say "Buzz"
    ] else [
      dup 3 mod 0 =
      iff [ drop say "Fizz" ]
      else [ echo ]
    ]
  ]
  cr
] is fizzbuzz

100 times [ 100 i - fizzbuzz ]
Enter fullscreen mode Exit fullscreen mode

Other than two extra empty lines at the end, it displays the usual FizzBuzz sequence.

Fibonacci

Let's do a very reasonable Fibonacci sequence:

[
  dup
  3 < iff [
    drop 1
  ] else [
    dup
    1 - fib
    swap
    2 - fib
    +
  ]
] is fib

[
  $ "fib(" echo$
  dup echo
  $ ")=" echo$
  fib echo
  cr
] is display-fib

20 times [ 20 i - display-fib ]
Enter fullscreen mode Exit fullscreen mode

Unfortunately it doesn't work, because Quackery doesn't support recursion:

$ quackery fib.qky
Unknown word: fib
Enter fullscreen mode Exit fullscreen mode

The second idea is to replace fib with recurse, but that just hangs, I'm guessing because it wouldn't recurse the funciton, only the else-block.

Somehow this weird code works, but I suspect only for this specific nesting level (recurse for first and ]this[ do for second):

[
  dup
  3 < iff [
    drop 1
  ] else [
    dup
    1 - ]this[ do
    swap
    2 - ]this[ do
    +
  ]
] is fib

[
  $ "fib(" echo$
  dup echo
  $ ")=" echo$
  fib echo
  cr
] is display-fib

20 times [ 20 i - display-fib ]
Enter fullscreen mode Exit fullscreen mode
$ quackery fib2.qky
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34
fib(10)=55
fib(11)=89
fib(12)=144
fib(13)=233
fib(14)=377
fib(15)=610
fib(16)=987
fib(17)=1597
fib(18)=2584
fib(19)=4181
fib(20)=6765
Enter fullscreen mode Exit fullscreen mode

There are ways to rewrite this to not use recursion, or only use it from the top level, but it's all just ridiculous.

Side Stacks

Quackery doesn't have "variables", but it has "side stacks", which are basically the same thing, except you can pop them to restore previous variable.

[ stack ] is name
[ stack ] is surname

[
  surname put
  name put
] is push-person
[
  name release
  surname release
] is pop-person
[
  $ "Hello, " echo$
  name share echo$
  $ " " echo$
  surname share echo$
  $ "!" echo$
  cr
] is display-person

$ "Harry" $ "Potter" push-person
$ "Hermione" $ "Granger" push-person
$ "Ron" $ "Weasley" push-person

display-person
pop-person
display-person
pop-person
display-person
Enter fullscreen mode Exit fullscreen mode
$ quackery sidestacks.qky
Hello, Ron Weasley!
Hello, Hermione Granger!
Hello, Harry Potter!
Enter fullscreen mode Exit fullscreen mode

Step by step:

  • we declare two side stacks, name and surname
  • we define push-person to push name and surname to the side stacks
  • we define pop-person to remove top name and surname from the side stacks
  • we define display-person to print Hello, #{name} #{surname}!
  • then we push 3 people, and display them in backwards order
  • <value> <side-stack> put pushes to a side stack
  • <side-stack> release> drops top value from a side stack
  • <side-stack> share copies top value from a side stack onto the main stack

This is how a lot of functionality in Quackery is implemented. For example that magic i for current loop iteration, that just takes times.count share. And that's why nested loops can work - and if you need iterator of some outer loop, well, you'll need to access times.count side stack directly.

This part of Quackery design is quite clever.

Should you use Quackery?

Considering how broken everything was for even the most basic things, no. And although I don't particularly love it, I think Factor (which changed how autoloading works after I posted the review, it's much better now) might currently be the best Forth-like to play with.

My advice to the creator of Quackery, to make the language more friendly:

  • package it so people can pip3 install quackery and then run quackery hello.qky without any problems
  • remove anti-Unicode features, just make emit print whatever
  • fix recursion to just work, this is dumb
  • support #! - basically ignoring the first line of any script if that's what it starts with
  • make cr use 10 (\n) not 13 (\r), it's just a weird pointless distraction

Code

All code examples for the series will be in this repository.

Code for the Quackery episode is available here.

Top comments (1)

Collapse
 
gordoncharlton profile image
Gordon Charlton • Edited

Hi, sorry for the slow reply, but I don't often vanity google.

Short version. I'd all but finished Quackery when my friends pushed me into putting it on GitHub. It was only ever a personal project, not intended for anyone's use other than my own, so everything is just the way I like it.

I'm a relic who remembers the cold war. So it's basically recreating my experience of using an Apple 2 at college, when a language came on a disk with a ring bound manual and a small amount of demo code. That's exactly what you get. A language that fits my requirements exactly, and the book I wish I'd had when I was 20. Exept now it's a download from GitHub with a pdf.

Thank you for your review. I very much enjoyed reading it. :-) And would like to address some points.

I'll look into this pip package thing.

' [ 72 101 108 108 111 44 32 87 111 114 108 100 33 13 ] witheach emit

Yes, that is painful. Exactly why there's the syntactic sugar of $ and say. Extending the compiler with "builders" is as simple as I could make it, if you want more sugar. :-)

No, it doesn't have "the absolutely worst Unicode support". It just doesn't support Unicode. I prefer it that way, because sending random numbers (which is what characters are) to Python and saying "print these characters" is a fairly good way of crashing Python abruptly and without a helpful error message. When that happened several times during development, and I was reading news reports about text messages that can brick your phone because of certain unicode sequences, I rather went to the other extreme and decided, "actually, newline, space and the ASCII printables are all I need, so everything else is a ? and that's fine." Go right ahead and remove the filtering in emit if you prefer. I've made the Python code as easy to understand as I can, so it is a trivial task to make trivial changes. Likewise if you want to have escape characters, go right ahead.

If you want to put a\n into a string, it's called carriage and use whatever nest editing words you like. join, poke or stuff spring to mind.

Incidentally, Forth still calls it CR.

times doesn't put the index on the stack because a lot of the time it isn't required, so you'd have to explicitly drop it.

  10 times [ drop say "Hello, world!" cr ] 
Enter fullscreen mode Exit fullscreen mode

Yeuch. Just put an i or an i^ where it's required and keep the stack clutter down.

If you want to count down to zero, use i. If you want to count up from zero use i^ (Pronounced "aye-up", which is amusing if you're from Yorkshire, which I am. Just be glad I stopped calling "print to screen" quack and changed it to the more subtle joke of echo.)

Yes, Quackery does have recursion. You can't automatically mention the name of a word you're defining until it has been named, and the naming word is comes after the definition. So you have to forward reference it. Like this.

               forward is fib
[ dup 3 < iff 
    [ drop 1 ]
  else
    [ dup
      1 - fib
      swap
      2 - fib
      + ] ]      resolves fib ( n --> n)

Enter fullscreen mode Exit fullscreen mode

There, fixed it. :-)

I'm glad you like the side stacks. I was well pleased with them and gave them a fancy name. "Ancillary" stacks.

The bit that pleased me most was the mix and match control flow wordset. I still get a buzz from writing code like

  [ 1+ dup 50 <  while
    over -1 peek 
    over coprime until
    over -2 peek 
    over coprime until
    2dup unused  until
    join 2       again ] 

Enter fullscreen mode Exit fullscreen mode

(context)

And even more so the meta-control flow words. (You can add new control flow words easily, and it doesn't even require extending the compiler.) (example: case)

Anyroad up, things that would please me, from least to most.

  1. Looking at Quackery and deciding you like it.
  2. Looking at the code and realising it's easy enough to fix the bits you don't like.
  3. Saying, "Ooh, concatenative and stack based languages are cool, but this is a bit toy-town", then looking at Forth and Factor and choosing the one that fits your requirements.
  4. Realising how easy it is to code a language like that, then being inspired to make your own, just the way you like it, because I'm here to tell you that coding in exactly the language you want is pure bliss.

TTFN, and BCNU, Gordon.