# The Number Guessing Game Written in YAML as LISP Interpreted with Python

I like the number guessing game, conceptually. Using this as a first example is especially rewarding in Python since it imposes so few syntax restrictions on its users.

When an absolute beginner looks at a piece of source code, no matter how simple, this is the perception:

Like the incomprehensible green letters in the Matrix, the source code with all its special characters like colons and parentheses looks like it came from another universe.

So, I tried to develop an even more straightforward way to introduce programming. Get rid of the data types, parenthesis and all this annoying stuff. Of course, I did not write a new language - and even if I did, it would not be very useful for everything else except teaching.

An often overused analogy to programming is that of a cooking recipe. And what does a recipe look like? Lots of bullet points, each indicating a step to perform. Sometimes, these bullet points are even nested, like so:

• mix ingredients a and b

• add ingredient c to the mix

Doesn’t that look like YAML? Oh, I hear you! YAML is not a programming language, and, even more important: It is certainly not a good choice if one wants to get rid of complex syntax rules. However, I decided to give it a try. What if our number guessing game could look like this:

``````- repeat:
- 3
- what:
- say:
- what: Please guess a number
- store:
- what: input
- to: number
- ifeq:
- val1:
- get_store:
- from: number
- val2: 777
- then:
- say:
- what: That was correct!
- break
- else:
- say:
- what: Try again!``````

That’s YAML, but it looks like LISP. I created a small Python parse for these programmes, and it turned out to work.

Here is the Parser code.

``````import sys
try:
except ImportError:

class ControlException(Exception): pass
class BreakException(ControlException): pass

class Parser:
def __init__(self, data):
""" Constructor takes the parsed yaml file
and initializes the variable storage."""
self.data = data    # parsed yaml
self._store = {}    # the variable store

def run(self):
"""This function is the entry point for the interpreter."""
for line in self.data:
self.exec_line(line)

def exec_line(self, line):
# If the line is a string, it could still be an exposed function,
# so call that.
if type(line) == str or type(line) == int:
return self.str_or_func(line)
name = list(line.keys())
fn = self.__getattribute__("exp_"+name)
return fn(line[name])

def str_or_func(self, x):
"""Returns the result of an exposed function,
or the string otherwise."""
try:
fn = self.__getattribute__("exp_"+x)
return fn()
except Exception as e:
# Our own ControlExceptions must be re-raised.
if isinstance(e, ControlException):
raise e
return x

def car(self, arg):
if type(arg) == dict:
return self.exec_line(arg)
if type(arg) == list:
res = [self.exec_line(code) for code in arg]
return res if len(res) == 1 else res
else:
res = arg
return self.exec_line(res)

exp_from = exp_else = exp_then = exp_val1 = exp_val2 = exp_what = car

def exp_break(self):
raise BreakException

def exp_get_store(self, arg):
fr = arg
s = self.exp_what(fr)
try:
return self._store.get(s)
except:
print("ERR", s)

def exp_store(self, arg):
kwargs = (arg | arg)
self._store[kwargs['to']] = self.exp_what(kwargs['what'])
# No return here, means, we have a statement, not an expression ;)

def exp_input(self):

def exp_say(self, *args):
for arg in args:
for code in arg:
what = self.exec_line(code)
print(what)

def exp_ifeq(self, arg):
val1 = self.exec_line(arg)
val2 = self.exec_line(arg)
if str(val1) == str(val2):
then = self.exec_line(arg)
return then
else:
try:
otherwise = self.exec_line(arg)
except IndexError:
pass
except:
raise

def exp_repeat(self, arg):
n = arg
for code in arg[1:]:
for i in range(int(n)):
try:
self.exec_line(code)
except BreakException:
break

def exp_concat(self, arg):
s  = ""
for code in arg:
s += str(self.exp_what(code))
return s

def exp_plus(self, arg):
return sum(int(self.exp_what(code)) for code in arg)

if __name__=="__main__":
parser = Parser(data)
parser.run()``````

Certainly not a production-ready LISP parser, but definitely enough to make the point in teaching.

Whenever a key in the YAML file has a corresponding `exp_<key>` callable in the parser class, this callable will then be invoked. The way we make these pseudo keyword arguments is by simply aliasing the names of these keywords to our car function.

### Why `car`?

`car` and `cdr` are primitive operations of the LISP programming language. `car` returns the first element of a list, while `cdr` returns the rest. In Python, this would look like this:

``````car = the_list
cdr = the_list[1:]``````

In essence, this is what my implementation of `car` does. It evaluates the list. Of course, there are some conceptual differences, such as evaluating all the way down recursively, but I still see it as a homage to LISP in that case.

I still need to try this YAML-LISP Number guessing game in my courses, but what do you think: Would the YAML programme make more sense to programming newbies, or does it lead to even more confusion? Of course, I would not cover the mechanics of the parser/interpreter as such in beginner’s courses.