Skip to content

Instantly share code, notes, and snippets.

@dtoma
Last active August 31, 2016 06:37
Show Gist options
  • Save dtoma/c7fb551d771f262a0f8e0d38996b6975 to your computer and use it in GitHub Desktop.
Save dtoma/c7fb551d771f262a0f8e0d38996b6975 to your computer and use it in GitHub Desktop.
FizzBuzz Lang
"""
Interpreter for a language targeted at solving fizzbuzz
Source: [The fastest fizzbuzz in the west](https://www.promptworks.com/blog/the-fastest-fizzbuzz-in-the-west)
## Imports
"""
from rply import LexerGenerator, ParserGenerator
from rply.token import BaseBox
import sys
"""
## Lexer
Ignores whitespaces.
Comments begin with `#`.
"""
lg = LexerGenerator()
lg.add('ELLIPSIS', r'\.\.\.')
lg.add('NUMBER', r'\d+')
lg.add('EQUALS', r'=')
lg.add('WORD', r'[a-z]+')
lg.ignore(r'\s+') # whitespace
lg.ignore(r'#.*\n') # comments
lexer = lg.build()
"""
## Boxes
> In RPython, like other statically typed languages, a variable must have a specific type.
> We take advantage of polymorphism to keep values in a box so that everything is statically typed.
> You can write whatever boxes you need for your project.
[Source](https://github.com/alex/rply#why-do-we-have-the-boxes)
"""
class IntBox(BaseBox):
def __init__(self, value):
self.value = int(value.getstr())
def int(self):
return self.value
class WordBox(BaseBox):
def __init__(self, value):
self.value = value.getstr()
def str(self):
return self.value
class RangeBox(BaseBox):
def __init__(self, low, high):
self.low = low
self.high = high
def range(self):
return range(self.low.int(), self.high.int() + 1)
"""
This one implements the fizzbuzz checks for a given integer.
"""
class AssignmentBox(BaseBox):
def __init__(self, word, number):
self.word = word
self.number = number
def eval_with(self, i):
if not i % int(self.number.int()):
return self.word.str()
return ''
class AssignmentsBox(BaseBox):
def __init__(self, assigments=None, assignment=None):
self.assigments = assigments
self.assignment = assignment
def list(self):
if self.assigments:
return self.assigments.list() + [self.assignment]
return []
class MainBox(BaseBox):
def __init__(self, range_box, assignments):
self.range_box = range_box
self.assignments = assignments
""" This runs every fizzbuzz rule on every line and prints the result """
def eval(self):
lines = []
for i in self.range_box.range():
line = str(i) + '\t'
for assignment in self.assignments.list():
line += assignment.eval_with(i)
lines.append(line)
return '\n'.join(lines) + '\n'
"""
## Parser
"""
pg = ParserGenerator([
'ELLIPSIS',
'EQUALS',
'NUMBER',
'WORD'
])
"""
### Rules
- the space before the ':' is mandatory, but sometimes missing in the tutorial
- the order of the rules is important: general to specific (opposite to that of the article)
"""
@pg.production('main : range assignments')
def main(p):
return MainBox(p[0], p[1])
@pg.production('assignments : assignments assignment')
def expr_assignments(p):
return AssignmentsBox(p[0], p[1])
@pg.production('assignments :')
def expr_empty_assignments(p):
return AssignmentsBox()
@pg.production('assignment : word EQUALS number')
def assignment_op(p):
return AssignmentBox(p[0], p[2])
@pg.production('range : number ELLIPSIS number')
def range_op(p):
return RangeBox(p[0], p[2])
@pg.production('number : NUMBER')
def number(p):
return IntBox(p[0])
@pg.production('word : WORD')
def word(p):
return WordBox(p[0])
parser = pg.build()
def begin(argv):
if len(argv) < 2:
print('./fizz_buzz_lang.py filename')
return
with open(argv[1], 'r') as f:
result = parser.parse(lexer.lex(f.read()))
print(result.eval())
"""
## main
Example code file:
```
0...15
fizz=3
buzz=5
```
Output:
```
0 fizzbuzz
1
2
3 fizz
4
5 buzz
6 fizz
7
8
9 fizz
10 buzz
11
12 fizz
13
14
15 fizzbuzz
```
"""
if __name__ == '__main__':
begin(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment